数据结构-顺序表详解(C语言版)
Posted 小小程序员...
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构-顺序表详解(C语言版)相关的知识,希望对你有一定的参考价值。
目录
顺序表概念及结构
概念:
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
注意:在顺序表中数据的存储是依次的。
顺序表的动态存储
如果空间不够,则进行增容。
顺序表的实现(C语言版本)
顺序表功能:
各功能的实现:
零:头文件部分
#pragma once
#include<stdio.h>
#include <assert.h>
#include <stdlib.h>
typedef int SeqDataType;
typedef struct SeqList
{
SeqDataType* p;
int size; //有效数据存储容量
int capacity; //总容量
}SeqList;
//初始化列表
void SeqListInit(SeqList* s);
//打印
void SeqListPrint(SeqList* s);
//销毁
void SeqListDestory(SeqList* s);
//检查
void SeqCheckCapacity(SeqList* s);
//插入数据
void SeqListInsert(SeqList* s, int pos, SeqDataType x);
//头插数据
void SeqListPushFront(SeqList* s, SeqDataType x);
//尾插数据
void SeqListPushBack(SeqList* pq, SeqDataType x);
//数据查找
int SeqListFind(SeqList* s, SeqDataType x);
//数据修改;
void SeqListModify(SeqList* s, int pos, SeqDataType x);
//删除数据
void SeqListErase(SeqList* s, int pos);
//头删数据
void SeqListPopFront(SeqList* s);
//尾删数据
void SeqListPopBack(SeqList* s);
一、初始化顺序表
void SeqListInit(SeqList* s)
{
assert(s);
s->p = NULL;
s->size = 0;
s->capacity = 0;
}
assert()进行断言,如果传入空指针,则提示报错。
初始化中,将size和capacity赋值0,将指针p赋为空指针。
二、打印顺序表
void SeqListPrint(SeqList* s)
{
assert(s);
for (int i = 0; i < s->size; ++i)
{
printf("%d ", s->p[i]);
}
printf("\\n");
}
同样的,首先断言传入的s是否为空指针。
由于顺序表的是数组存储并且时依次存储,所以打印顺序表数据时可采用简单的循环进行打印。
三、顺序表的销毁
void SeqListDestory(SeqList* s)
{
assert(s);
free(s->p);
s->p = NULL;
s->size = 0;
s->capacity = 0;
}
销毁时,首先一定要断言s是否为空指针,否则可能会出现一些问题。
如果传入的s已经被销毁且置为空指针,那么再次销毁时free(s),即将同一块空间释放了两次,报错,所以一定要加入断言。
在断言之后,首先释放空间,将指针p置为空指针,由于销毁之后顺序表为空表,没有存储数据和开辟空间,所以将size和capacity均置为0。
四、检查顺序表
void SeqCheckCapacity(SeqList* s)
{
if (s->size == s->capacity)
{
int newcapacity = (s->capacity == 0 ? 4 : s->capacity * 2);
SeqDataType* newA = realloc(s->p, sizeof(SeqDataType) * newcapacity);
if (NULL == newA)
{
printf("realloc fail\\n");
exit(-1);
}
s->p = newA;
s->capacity = newcapacity;
}
}
检查顺序表,即检查是否有多余的空间进行数据的存储,如果空间足够,则无操作,反之进行增容操作。
检查时,如果已经存储的数据大小小于空间的大小,那么可以存储数据,无需增容。
检查时,如果已经存储的数据大小和开辟空间的大小相等,那么说明顺序表空间已满,要存储数据则需要进行增容。
需要增容时分两种情况:
1.如果顺序表不是空表,那么将空间增容至其原空间的两倍。
!!!!!下面注意了!!!!!
2.如果顺序表是空表,那么size和capacity均为0,两者也是相等,如果按照上面同样增容至两倍,空间依旧是0。所以这里要分类讨论,如果空间为0,则将空间置为4,如果不为0,则扩容2倍。
int newcapacity = (s->capacity == 0 ? 4 : s->capacity * 2);
扩容时通过realloc进行扩容,这里同样是分为两种情况。
1.如果原空间较小,扩容至两倍时,在原空间地址之后的有足够的空间进行扩容,这里称为原地扩容,即扩容后数组的首地址不变。
2.如果原空间过大,扩容后在存储空间后没有足够的空间进行扩容,那么系统将重新开辟一块空间进行扩容,将原来的数据复制过去,这里称为异地扩容,扩容后数组的首地址会变。
SeqDataType* newA = realloc(s->p, sizeof(SeqDataType) * newcapacity);
if (NULL == newA)
{
printf("realloc fail\\n");
exit(-1);
}
s->p = newA;
s->capacity = newcapacity;
如果扩容不成功,则提示并退出程序。
如果成功,将p赋值为newA(newA可能与p相同也有可能不同,即上面说的两种情况),并将capacity(空间大小)更改为新的大小。
五、插入数据
void SeqListInsert(SeqList* s, int pos, SeqDataType x)
{
assert(s);
assert(pos >= 0 && pos <= s->size);
SeqCheckCapacity(s);
int end = s->size - 1;
while (end >= pos)
{
s->p[end + 1] = s->p[end];
end--;
}
s->p[pos] = x;
s->size++;
}
传参分别是:顺序表的指针,要插入数据的位置,和要插入数据的值。
首先判断顺序表指针是否为空指针,其次判断要插入的位置是否合法。
这里要注意:边界条件,当pos为0是,代表在整个顺序表的头部插入,当pos为size的值是,
代表在整个顺序表的尾部插入。
assert(s);
assert(pos >= 0 && pos <= s->size);
在判断完之后,通过SeqCheckCapacity(s)函数进行检查,检查是否有空间进行插入数据。
插入数据时,将插入位置后面的数据向后移动一个位置,从而空出空间插入新的数据
插入数据后将size(数据的个数)的值+1。
六、头插和尾插
void SeqListPushFront(SeqList* s, SeqDataType x)
{
SeqListInsert(s, 0, x);
}
void SeqListPushBack(SeqList* pq, SeqDataType x)
{
SeqListInsert(pq, pq->size, x);
}
在顺序表中,一般情况下用到最多的就是头插和尾插了,而头插和尾插函数,就是调用了五中的插入函数,将位置参数分别设置为0和size即可,这里体现了代码的复用,便捷可靠。
七、查找数据
int SeqListFind(SeqList* s, SeqDataType x)
{
assert(s);
for (int i = 0; i < s->size; ++i)
{
if (s->p[i] == x)
{
return i;
break;
}
}
return -1;
}
在顺序表中查找某个数据,本质上就是在数组中查找,这里通过遍历的方法,如果找到该数据,就返回查找到的第一个数据的数组的下标,如果没有找到就返回-1。
八、修改数据
void SeqListModify(SeqList* s, int pos, SeqDataType x)
{
assert(s);
assert(pos >= 0 && pos < s->size);
s->p[pos] = x;
}
传参与插入数据相同,传入顺序表指针,位置和数据值。
首先断言顺序表和位置是否合法,这里与插入不同,因为是修改数据,所以原数据一定是存在的,
所以pos的值不能为size,但可以为0,这是因为数据在数组中存储,下标是从0开始的,这里要注意边界条件的区别。
判断之后直接通过下标修改数据即可。
九、删除数据
void SeqListErase(SeqList* s, int pos)
{
assert(s);
assert(pos >= 0 && pos < s->size);
int begin = pos;
while (begin <= s->size - 1)
{
s->p[begin] = s->p[begin + 1];
++begin;
}
s->size--;
}
该函数传参为顺序表指针和要删除的位置。
首先断言指针和位置是否合法,与八中的逻辑相同,在这之后将该位置之后的所有数据向前挪动,覆盖之前的一个数据,覆盖完成后将size-1。
这里有人可能会感到疑惑,覆盖之后,最后一个位置的数据怎么办?
这里我们不做处理,因为size的值已经将顺序表的数据范围框定,而没有数据的位置上的值原本就是随机值,所以我们不做处理。
十、头删和尾删
void SeqListPopFront(SeqList* s)
{
SeqListErase(s, 0);
}
void SeqListPopBack(SeqList* s)
{
SeqListErase(s,s->size-1);
}
这里是复用九中的代码,将位置参数改为0和size-1即可。
顺序表代码:
SeqList.h:
#pragma once
#include<stdio.h>
#include <assert.h>
#include <stdlib.h>
typedef int SeqDataType;
typedef struct SeqList
{
SeqDataType* p;
int size; //有效数据存储容量
int capacity; //总容量
}SeqList;
//初始化列表
void SeqListInit(SeqList* s);
//打印
void SeqListPrint(SeqList* s);
//销毁
void SeqListDestory(SeqList* s);
//检查
void SeqCheckCapacity(SeqList* s);
//插入数据
void SeqListInsert(SeqList* s, int pos, SeqDataType x);
//头插数据
void SeqListPushFront(SeqList* s, SeqDataType x);
//尾插数据
void SeqListPushBack(SeqList* pq, SeqDataType x);
//数据查找
int SeqListFind(SeqList* s, SeqDataType x);
//数据修改;
void SeqListModify(SeqList* s, int pos, SeqDataType x);
//删除数据
void SeqListErase(SeqList* s, int pos);
//头删数据
void SeqListPopFront(SeqList* s);
//尾删数据
void SeqListPopBack(SeqList* s);
SeqList.c
#include"SeqList.h"
void SeqListInit(SeqList* s)
{
assert(s);
s->p = NULL;
s->size = 0;
s->capacity = 0;
}
void SeqListPrint(SeqList* s)
{
assert(s);
for (int i = 0; i < s->size; ++i)
{
printf("%d ", s->p[i]);
}
printf("\\n");
}
void SeqListDestory(SeqList* s)
{
assert(s);
free(s->p);
s->p = NULL;
s->size = 0;
s->capacity = 0;
}
void SeqCheckCapacity(SeqList* s)
{
if (s->size == s->capacity)
{
int newcapacity = (s->capacity == 0 ? 4 : s->capacity * 2);
SeqDataType* newA = realloc(s->p, sizeof(SeqDataType) * newcapacity);
if (NULL == newA)
{
printf("realloc fail\\n");
exit(-1);
}
s->p = newA;
s->capacity = newcapacity;
}
}
void SeqListInsert(SeqList* s, int pos, SeqDataType x)
{
assert(s);
assert(pos >= 0 && pos <= s->size);
SeqCheckCapacity(s);
int end = s->size - 1;
while (end >= pos)
{
s->p[end + 1] = s->p[end];
end--;
}
s->p[pos] = x;
s->size++;
}
void SeqListPushFront(SeqList* s, SeqDataType x)
{
SeqListInsert(s, 0, x);
}
void SeqListPushBack(SeqList* pq, SeqDataType x)
{
SeqListInsert(pq, pq->size, x);
}
int SeqListFind(SeqList* s, SeqDataType x)
{
assert(s);
for (int i = 0; i < s->size; ++i)
{
if (s->p[i] == x)
{
return i;
break;
}
}
return -1;
}
void SeqListModify(SeqList* s, int pos, SeqDataType x)
{
assert(s);
assert(pos >= 0 && pos < s->size);
s->p[pos] = x;
}
void SeqListErase(SeqList* s, int pos)
{
assert(s);
assert(pos >= 0 && pos < s->size);
int begin = pos;
while (begin <= s->size - 1)
{
s->p[begin] = s->p[begin + 1];
++begin;
}
s->size--;
}
void SeqListPopFront(SeqList* s)
{
SeqListErase(s, 0);
}
void SeqListPopBack(SeqList* s)
{
SeqListErase(s,s->size-1);
}
test.c
#include"SeqList.h"
int main()
{
SeqList s;
SeqListInit(&s);
SeqListPushBack(&s,1);
SeqListPushBack(&s,2);
SeqListPushBack(&s,3);
SeqListPushBack(&s,4);
SeqListPrint(&s);
SeqListPushFront(&s,0);
SeqListPrint(&s);
SeqListPopFront(&s);
SeqListPrint(&s);
SeqListPopBack(&s);
SeqListPrint(&s);
return 0;
}
以上便是顺序表实现的全过程了。
创作不易,希望大家,点赞、收藏、关注!!!
如果本篇博客有任何错误,请批评指教,不胜感激 !
以上是关于数据结构-顺序表详解(C语言版)的主要内容,如果未能解决你的问题,请参考以下文章
数据结构(C语言版)严蔚敏(线性表队列栈数组树图等数据结构参考代码,持续更新中。。。)
数据结构(C语言版)严蔚敏(线性表队列栈串树图等数据结构参考代码,持续更新中。。。)