数据结构 -- 顺序表
一、基础知识
- 表中的元素具有线性关系,我们称之为线性表,有唯一的第一个元素和最后一个元素。
- 除了第一个元素和最后一个元素,其余每个元素都有唯一的前驱和后继。
- 我们称这些元素为结点。
线性表通常有两种实现方式,顺序表和链表,这篇文章主要讲解顺序表
二、什么是顺序表
- 将线性表中的元素按照逻辑顺序关系,依次存储在地址连续的存储空间中,通常是使用数组来实现。
- 顺序表在逻辑和物理上都是连续的。
- 支持随机访问,因为存储在数组中,所以他可以通过索引来访问结点。
- 顺序存储的优点是支持随机访问,他的缺点是在开辟的时候内存大小已经确定,后期更改的话需要重新分配内存空间。
三、顺序表的实现
1. 初始化
定义一个结构体SeqList
用来存储结点中的信息并且设置好需要用到的宏定义。
# define Size 5
# define True 1
# define False 0
typedef struct SeqList
{
int* data;
int count;//当前表中元素
int cap;//元素最大个数
}List;
void InitList(List* p)
{
List* p->data = (int*)malloc(sizeof(int)*Size);
if(p->data == NULL)
{
//开辟失败
exit(1);
}
memset(p->data,0,sizeof(int)*Size);//使用memset函数初始化这段空间
p->count = 0;
p->cap = Size;
}
memset
函数包含在头文件string.h
中,用来初始化。
2. 销毁
void DestroyList(List* p)
{
assert(p);//assert宏判断是否为真,不为真终止程序,这里用来判断p是否存在
free(p->data);
p->data = NULL;//安全措施,虽然free掉了,但是避免悬挂指针的存在
p->cap = 0;
p->count = 0;
}
assert
宏判断是否为真,不为真终止程序,这里用来判断p是否存在- 虽然
free
掉了p->data
,但是为了安全还是要将p->data
指向空,避免悬挂指针的出现。
3. 判断空,满
int IsEmpty(List* p)
{
assert(p);
if(p->count == 0)
{
printf("空表\n");
return True;
}
return False;
}
int IsFull(List* p)
{
assert(p);
if(p->count == p->cap)
{
return True;
}
return False;
}
- 是空是满返回True,不空不满返回False,开头有宏定义。
4. 清空表
void ClearList(List* p)
{
if(IsEmpty(p) == True)
{
printf("空表啊,清空个der~\n");
return;
}
memset(p->data,0,sizeof(int)*Size);
p->count =0;
}
- 把表清空就是将所有元素置为空,然后计数器为0。
5. 打印表
void PrintList(List* p)
{
if(IsEmpty(p) == True)
{
printf("空表打印个der~\n");
return;
}
int i;
for(i=0;i<p->count;i++)
{
printf("%d ",p->data[i]);
}
putchar('\n');
}
6. 头插
void InsertHead(List* p,int num)
{
if(IsFull(p) == True)
{
printf("满了满了,塞不进来了\n");
return;
}
int i = p->count;
for(i;i>0;i--)
{
p->data[i] = p->data[i-1];
}
p->data[0] = num;
p->count++;
}
7. 尾插
尾插很简单,数组不满,直接将元素放在最后一个位置就行
void InsertBack(List* p,int num)
{
if(IsFull(p) == True)
{
printf("满了满了,别插了\n");
return;
}
p->data[p->count] = num;
p->count++;
}
8. 指定位置插入
void InsertPosition(List* p,int num,int index)
{
if(IsFull(p) == True)
{
printf("满了满了,别插了\n");
return;
}
int i = p->count;
if(index<0||index>(p->count-1))
{
printf("下标错误\n");
return;
}
for(i;i>index;i--)
{
p->data[i] = p->data[i-1];
}
p->data[index] = num;
p->count++;
}
9. 头删
void PopHead(List* p)
{
if(IsEmpty(p) == True)
{
printf("空的删鸡毛诶~\n");
return;
}
int i;
for(i = 0;i<p->count;i++)
{
p->data[i] = p->data[i+1];
}
p->count--;
}
10. 尾删
尾删就简单了,直接让最后一个元素为空就行了。
void PopBack(List* p)
{
if(IsEmpty(p) == True)
{
printf("空的哥,服啦\n");
return;
}
p->data[p->count-1] =NULL;
p->count--;
}
11.指定元素删除
void PopNum(List* p,int num)
{
if(IsEmpty(p) == True)
{
printf("空的哥,服啦\n");
return;
}
int i,j,k;
k = p->count;
for(i=0;i<p->count;i++)
{
if(p->data[i] == num)
{
for(j = i;j<p->count;j++)
{
p->data[j] = p->data[j+1];
}
p->count--;
}
}
if(p->count == k)//k保留之前的长度,如果长度不变,说明没有删除任何元素,那么就是没找到这个num元素。
{
printf("没找到这个元素\n");
}
}
12.指定位置删除
和根据元素删除很相似,找到这个位置后,后面向前覆盖就行。
void PopIndex(List* p,int index)
{
if(IsEmpty(p) == True)
{
printf("空的哥,服啦\n");
return;
}
if(index<0||index>=p->count)
{
printf("下标错误\n");
return;
}
int i;
for(i = index;i<p->count;i++)
{
p->data[i] = p->data[i+1];
}
p->count--;
}
13.指定位置更改元素
在顺序表中,指定位置更改只需要把这个下标的元素换一下就OK,非常简单
void ChangeIndex_Num(List* p,int index,int num)
{
if(index<0||index>=p->count)
{
printf("下标错误\n");
return;
}
p->data[index] = num;
}
14.指定元素更改
逻辑是从前向后遍历,如果找到这个元素就更改。
void ChangeNum_Num(List*p,int num;int new)
{
int i,flag;
flag = False;
for(i = 0;i<p->count;i++)
{
if(p->data[i] == num)
{
p->data[i] == new;
flag = True;
}
}
if(flag == False)
{
printf("没找到这个元素\n");
}
}
15. 查找元素下标
逻辑是从前向后遍历,找到了就返回下标。
int SearchNum_Index(List* p,int num)
{
int i;
for(i =0;i<p->count;i++)
{
if(p->data[i] == num)
{
return i;
}
}
}
16.根据下标查找元素
逻辑是直接返回这个下标的元素就行,数组有可以通过下标直接访问的特性。
int SearchIndex_Num(List* p,int index)
{
//判断下标是否超出,懒得判断了
return p->data[index];
}
四、总结
- 数据结构无非就是增删改查,顺序表的本质是数组,就是在操作数组。
- 牢记数组三大特性:访问有序性,存储连续性,类型一致性。
- 在进行指定位置的增加和删除的时候,一定要注意元素覆盖的顺序,一定要避免将元素丢失和覆盖。