1.线性表
1.什么是线性表?
如果我们想要存储数据,最常见的就是数据与数据之间一一联系,前一个数据和后一个数据相连,这种数据结构最后会像线一样连成一串,这种关系逻辑被称为线性结构,线性表根据物理结构分为顺序表和链表,顺序表的物理地址是连续的,链表的物理地址使用一根线连接
2. 顺序表
1.什么是顺序表?
顺序表是用一段物理地址连续的储存单元依次储存元素的线性结构,一般情况下采用数组储存,顺序表可以分为静态顺序表和动态顺序表
2.静态顺序表
我们可以创建一个结构体,结构体成员包括数组arr和记录数据个数size,每将一个数据储存到数组中,size的大小加1,静态顺序可以容纳数据的大小在数组创建时就已经决定
#define N 10 //通过更改N的值改变数组的长度
typedef int SLDataType //可以把int改为数据类型
typedef struct SeqList
{
SLDataType arr[N]; //数组的长度固定
int size;
}SeqList;
3.动态顺序表
静态顺序表只适用于确定知道要存储多少数据的时候,静态顺序表的定长数组,如果数组过大浪费空间,如果数组过小不够用,所以动态顺序表更加实用,根据需要的数据大小动态分配大小
关于动态内存管理的内容可以参考:(c语言)动态内存管理
- 创建一个结构体struct SeqList,结构体成员包括一个指向动态开辟数组的指针a,有效数据的个数size,数据容量空间的大小capacity
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* a;//指向动态开辟数组
int size; //有效数据的个数
int capacity; //容量空间的大小
}SeqList;
- 创建一个函数void InitSqList(SeqList* pc)初始化顺序表,断言指针pc不为空,通过malloc函数申请Capacity个整形大小空间,初始化pc->size=0,pc->capacity=Capacity
//顺序表的初始化
void InitSqList(SeqList* pc)
{
assert(pc);
SLDataType* p = NULL;
p = (SLDataType*)malloc(Capacity * sizeof(SLDataType));
if (pc == NULL)
{
perror("InitSqList");
exit(-1);
}
pc->a = p;
pc->size = 0;
pc->capacity = Capacity;
}
- 创建一个函数void DestorySqList(SeqList* pc)销毁顺序表,断言指针pc不为空,释放pc->a指向的空间并将指针置空,将pc->size=pc->capacity=0
//顺序表的销毁
void DestorySqList(SeqList* pc)
{
assert(pc->a);
free(pc->a);
pc->a = NULL;
pc->size = 0;
pc->capacity = 0;
}
- 创建一个函数void PrintSqList(SeqList* pc)打印顺序表,遍历并打印pc->a的元素
//打印顺序表
void PrintSqList(SeqList* pc)
{
assert(pc);
int i = 0;
for (i = 0; i < pc->size; i++)
{
printf("%d ", pc->a[i]);
}
printf("\n");
}
- 创建一个函数void CheckCapacity(SeqList* pc)检查容量是否为满,判断如果pc->size==p->capacity,使用realloc函数扩容为两倍原容量
//检查容量是否已满、
#define Capacity 4
void CheckCapacity(SeqList* pc)
{
assert(pc);
if (pc->capacity == pc->size)
{
SLDataType* tmp = (SLDataType*)realloc(pc->a, 2 * (pc->capacity) * sizeof(SeqList));
if (tmp == NULL)
{
perror("CheckCapacity");
exit(-1);
}
pc->a = tmp;
pc->capacity *= 2;
//printf("增容成功\n");
}
}
- 创建一个函数void SqListInsert(SeqList* pc, int pos, int val),断言插入的位置pos>=0&&pos<=pc->size,首先使pc->size++,使用CheckCapacity函数检查是否容量已满,然后从后往前挪动覆盖数据(从前往后挪动数据,后面的数据会被覆盖),最后p->arr[pos]=val
在挪动覆盖数据时注意:创建一个变量end用于遍历pos之前的元素,在此之前pc->size++,所以将end赋初值为pc->size-2,让pc->a[end + 1] = pc->a[end], 每次end--直到end>=pos
//在指定位置插入元素
void SqListInsert(SeqList* pc, int pos, int val)
{
assert(pos >= 0 && pos <= pc->size);
assert(pc);
pc->size++;
CheckCapacity(pc);
int end = pc->size-2;
while (end>=pos)
{
pc->a[end + 1] = pc->a[end];
end--;
}
pc->a[pos] = val;
}
- 创建一个函数void SqListDel(SeqList* pc, int pos),pos >= 0 && pos < pc->size,从前向后挪动覆盖数据,最后pc->size--
在挪动覆盖数据时注意:创建一个变量end用于遍历pos之前的元素,将end赋初值为pc->size,让pc->a[end] = pc->a[end + 1], 每次end++直到end<pc->size-1
//弹出指定位置的元素
void SqListDel(SeqList* pc, int pos)
{
assert(pos >= 0 && pos < pc->size);
assert(pc);
int end = pos;
while (end<pc->size-1)
{
pc->a[end] = pc->a[end + 1];
end++;
}
pc->size--;
}
- 创建SqListPushback函数在尾部插入元素时,直接使用SqListInsert(pc, pc->size, x)
- 创建SqListPushFront函数在首部插入元素时,直接使用SqListInsert(pc, 0, x)
- 创建SqListPopBack函数弹出尾部元素时,直接pc->size--即可,不会影响后续各种操作并且足够简洁
- 创建SqListPopFront函数弹出首部元素时,直接使用SqListDel(pc,0)
//在尾部插入元素
void SqListPushback(SeqList *pc, SLDataType x)
{
SqListInsert(pc, pc->size, x);
}
//弹出尾部元素
void SqListPopBack(SeqList* pc)
{
assert(pc->size > 0);
assert(pc);
pc->size--;
}
//在首部插入元素
void SqListPushFront(SeqList* pc, SLDataType x)
{
SqListInsert(pc, 0, x);
}
//弹出首部元素
void SqListPopFront(SeqList* pc)
{
assert(pc->size > 0);
assert(pc);
SqListDel(pc,0);
}
3.单链表
顺序表在中间或头部的插入删除,需要把数据向后挪动,时间复杂度为O(N)
扩容空间使用realloc函数,如果原空间后面空间不足,需要把原数据拷贝到新空间,再释放原空间,会有不小的消耗
扩容一般是以2倍空间扩容,会有一定空间的浪费。假如空间是100,扩容两倍空间变成200,我们使用了101空间,剩余99空间
链表的物理储存结构是非连续的,非顺序的储存结构,数据元素的逻辑顺序是通过链表的指针链接实现的,这样就可以解决顺序表中间插入时间复杂度高和空间浪费的问题
- 创建一个结构体struct SlistNode相当于一个链表节点,一个链表储存了SLTData类型的变量和下一个链表节点的地址,通过解引用就可以访问下一个节点
- 创建头指针,头指针指向第一个节点
typedef int SLTData;
typedef struct SListNode
{
SLTData Data;
struct SListNode* next;
}SLTNode;
//主函数
void test()
{
SLTNode* head = NULL;//创建头指针,头指针指向第一个节点
int j = 0;
printf("请输入想要输入数据的个数:>");
scanf("%d", &j);
int i = 0;
printf("请依次输入数据:>");
for (i = 0; i < j; i++)
{
int x = 0;
scanf("%d", &x);
SLTPushBack(&head, x);
}
SLTPrint(phead);
SListInsertAfter(&head, head->next, 10);
SListInsert(&head, head->next, 20);
SLTPrint(head);
}
int main()
{
test();
return 0;
}
- 创建SLTNode* AddSLTNode()函数用于增加一个新的节点,使用malloc函数申请一个STLNode大小的空间,将这个新节点NewNode->next=NULL,返回指向该空间的指针
//增加一个新的节点
SLTNode* AddSLTNode()
{
SLTNode* NewNode = (SLTNode*)malloc(sizeof(SLTNode));
if (NewNode == NULL)
{
perror("AddSLTNode");
exit(-1);
}
NewNode->next = NULL;
return NewNode;
}
- 创建void SLTPrint(SLTNode* plist)函数打印所有节点,打印每一个节点的plist->Data,每次打印plist=plist->next先后移东一个节点直到plist为空,最后打印NULL空指针表示停止
//打印所有节点
void SLTPrint(SLTNode* plist)
{
while (plist)
{
printf("%d->", plist->Data);
plist = plist->next;
}
printf("NULL\n");
}
- 创建void SLTPushFront(SLTNode** phead, SLTData x)函数头插,注意:因为需要修改phead形参类型为二级指针,首先申请一个新的节点NewNode,NewNode->Data=x如果phead为NULL,*phead直接指向NewNode,否则让NewNode->next = *phead指向头节点,*phead = NewNode指向新节点
//头插
void SLTPushFront(SLTNode** phead, SLTData x)
{
assert(phead);
SLTNode* NewNode = AddSLTNode();
NewNode->Data = x;
if(*phead==NULL)
*phead = NewNode;
else
{
NewNode->next = *phead;
*phead = NewNode;
}
}
- 创建void SLTPopFront(SLTNode** phead)函数头删(我们释放头节点,让头指针指向下一个节点,如果我们直接释放头节点无法找到下个节点,所以我们需要一个指针储存头指针,头指针移动到下一个节点)创建一个plist = *phead,*phead = (*phead)->next指向下一个节点,最后free(plist)释放头节点
//头删
void SLTPopFront(SLTNode** phead)
{
assert(phead);
assert(*phead);
SLTNode* plist = *phead;
*phead = (*phead)->next;
free(plist);
}
- 创建 void SLTPushBack(SLTNode** phead, SLTData x)函数尾插,首先申请一个新的节点NewNode,NewNode->Data=x如果phead为NULL,*phead直接指向NewNode,否则找到指向最后一个尾节点的指针,让尾节点plist->next = NewNode指向新节点(创建指针plist=*phead,每次plist=plist->next直到plist->next为空)
//尾插
void SLTPushBack(SLTNode** phead, SLTData x)
{
assert(phead);
SLTNode* NewNode = AddSLTNode();
NewNode->Data = x;
SLTNode* plist = *phead;
if (*phead == NULL)
{
*phead = NewNode;
}
else
{
while (plist->next)
{
plist = plist->next;
}
plist->next = NewNode;
}
}
- 创建SLTNode* SListFind(SLTNode* phead, SLTData x)函数查找某个数,遍历列表每次phead = phead->next直到找到phead->Data == x或者phead=NULL退出循环返回空指针
//查找某个数
SLTNode* SListFind(SLTNode* phead, SLTData x)
{
assert(phead);
while (phead)
{
if (phead->Data == x)
{
return phead;
}
phead = phead->next;
}
return phead;
}
- 创建void SListInsert(SLTNode** phead, SLTNode* pos, SLTData x)函数在pos位置之前插入x,如果*phead==pos相当于头插直接SLTPushFront(phead, x),每次plist = plist->next直到plist->next=pos,然后创建一个新节点NewNode->Data = x,NewNode->next = plist->next,plist->next = NewNode
//在pos位置之前插入x
void SListInsert(SLTNode** phead, SLTNode* pos, SLTData x)
{
assert(phead);
assert(pos);
if (*phead == pos)
{
SLTPushFront(phead, x);
}
else
{
SLTNode* plist = *phead;
while (plist->next!=pos)
{
plist = plist->next;
}
SLTNode* NewNode = AddSLTNode();
NewNode->Data = x;
NewNode->next = plist->next;
plist->next = NewNode;
}
}
- 创建void SListInsertAfter(SLTNode** phead, SLTNode* pos, SLTData x)函数在pos位置之后插入,首先创建一个新节点NewNode,NewNode->next = pos->next新节点链接pos指向节点的下一个节点,pos->next = NewNode指针pos指向的节点指向NewNode
//在pos位置之后插入
void SListInsertAfter(SLTNode** phead, SLTNode* pos, SLTData x)
{
SLTNode* NewNode = (SLTNode*)malloc(sizeof(SLTNode));
NewNode->Data = x;
NewNode->next = pos->next->next;
pos->next = NewNode;
}
总结:
- SLTNode* AddSLTNode()函数用于增加一个新的节点,函数的作用就是单纯创建一个新的节点,并返回指向该节点的指针,NewNode->next=NULL这个保证了后面的函数void SLTPrint(SLTNode* plist)需要找到NULL
- 头插和尾插都需要考虑链表中有没有元素,如果插入的是第一个元素(即*phead==NULL),需要修改*phead=NewNode(即修改头节点head)
如果想要修改头指针head需要取head的地址,将二级指针传参给函数,void SLTPushBack(SLTNode* phead, SLTData x)这样定义函数,改变phead是无法改变真正的头指针的,改变的是形参、
4.双向带头链表
哨兵位:该节点是个无效节点,不存储任何有效信息,但使用它可以方便我们头尾插和头尾删时不用判断头节点指向NULL的情况,同时也不需要改变头指针的指向,也就不需要传二级指针了。
- 创建一个结构体,结构体成员为指向上一个节点的指针prev,指向下一个节点的指针next,存放LTDatetype类型数据的变量Data
typedef int LTDatatype;
typedef struct ListNode
{
struct ListNode* prev;
struct ListNode* next;
LTDatatype Data;
}ListNode;
//主函数
TestList3()
{
ListNode* phead = InitList();
STListPushBack(phead, 1);
STListPushFront(phead, 10);
STListPushFront(phead, 20);
STListPrint(phead);
ListNode* pos = STListFind(phead, 10);
STListInsert(phead, pos, 30);
STListPrint(phead);
STListErase(phead, pos);
STListPrint(phead);
free(phead);
}
int main()
{
TestList3();
return 0;
}
- 创建SLTNode* AddSLTNode()函数用于增加一个新的节点,使用malloc函数申请一个STLNode大小的空间,返回指向该空间的指针
ListNode* BuySTList()
//创建一个新的节点
{
ListNode* NewNode = (ListNode*)malloc(sizeof(ListNode));
return NewNode;
}
- 创建ListNode* InitList()函数初始化哨兵位,让phead->next = phead,phead->prev = phead全部指向phead哨兵位自己,同时phead->Data=0存放的数据为0,返回哨兵位
ListNode* InitList()
//初始化哨兵位
{
ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
phead->next = phead;
phead->prev = phead;
phead->Data = 0;
return phead;
}
- 创建 ListNode* DestorySTList(ListNode* phead)函数销毁所有数据,遍历链表中所有节点,依次释放空间
ListNode* DestorySTList(ListNode* phead)
//销毁所有数据
{
assert(phead->next);
phead = phead->next;
ListNode* first = phead;
while (first!=phead)
{
phead = phead->next;
free(first);
first = phead;
}
}
- 创建void STListPrint(ListNode* phead)函数打印节点,依次遍历每一个节点,打印每一个节点的数据
void STListPrint(ListNode* phead)
//打印节点
{
assert(phead);
ListNode* first = phead->next;
printf("phead=>");
while (first!=phead)
{
printf("%d<=>", first->Data);
first = first->next;
}
printf("\n");
}
- 创建ListNode* STListFind(ListNode* phead, LTDatatype x)函数查找节点,还是像上面一样遍历所有节点,如果找到first->Data == x,返回指向该节点的指针
ListNode* STListFind(ListNode* phead, LTDatatype x)
//查找节点
{
assert(phead);
ListNode* first = phead->next;
while (first != phead)
{
if (first->Data == x)
return first;
first = first->next;
}
return NULL;
}
- 创建void STListInsert(ListNode* phead, ListNode* pos, LTDatatype x)函数在pos前位置插入节点,首先创建一个新节点NewNode,通过pos->prev找到pos上一个节点,first->next = NewNode;NewNode->prev = first,NewNode->next = pos,pos->prev = NewNode
void STListInsert(ListNode* phead, ListNode* pos, LTDatatype x)
//在pos前位置插入节点
{
assert(phead);
assert(pos);
ListNode* NewNode = BuySTList();
NewNode->Data = x;
ListNode* first = pos->prev;
first->next = NewNode;
NewNode->prev = first;
NewNode->next = pos;
pos->prev = NewNode;
}
- 创建void STListErase(ListNode* phead, ListNode* pos)函数删除pos位置的节点,直接通过pos->prev和pos->next找到pos上一个节点和下一个节点,first->next = second,second->prev = first
void STListErase(ListNode* phead, ListNode* pos)
//删除pos位置的节点
{
assert(phead);
assert(phead->next != phead);
ListNode* first = pos->prev;
ListNode* second = pos->next;
free(pos);
first->next = second;
second->prev = first;
}
- 头插尾插直接使用void STListInsert(ListNode* phead, ListNode* pos, LTDatatype x)函数
- 头删尾删直接使用void STListErase(ListNode* phead, ListNode* pos)函数
void STListPushBack(ListNode* phead, LTDatatype x)
//尾插
{
STListInsert(phead, phead->prev, x);
}
void STListPopBack(ListNode* phead)
//尾删
{
STListErase(phead, phead->prev);
}
void STListPushFront(ListNode* phead, LTDatatype x)
//头插
{
STListInsert(phead, phead->next, x);
}
void STListPopFront(ListNode* phead)
//头删
{
STListErase(phead, phead->next);
}