文章目录
顺序表和链表
1. 线性表
1.线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是⼀种在实际中⼴泛使 ⽤的数据结构,常⻅的线性表:顺序表、链表、栈、队列、字符串…
2.线性表在逻辑上是线性结构,也就说是连续的⼀条直线。但是在物理结构上并不⼀定是连续的, 线性表在物理上存储时,通常以数组和链式结构的形式存储。
2.顺序表
(1).顺序表是⽤⼀段物理地址连续的存储单元依次存储数据元素的线性结构,⼀般情况下采⽤数组存储。
(2).顺序表和数组的区别?
顺序表的底层结构是数组,对数组的封装,实现了常⽤的增删改查等接⼝。
2.1 概念与结构
2.2 分类
2.2.1静态顺序表
概念:使用定长数组储存元素。
//静态顺序表
typedef int SLDataType;
#define N 7;
typedef struct SeqList {
SLDataType a[N];//定长数组
int size;//有效数据个数
}SL;
静态顺序表缺陷:空间给少了不够⽤,给多了造成空间浪费。
2.2.2动态顺序表
//动态顺序表--按需申请
typedef struct SeqList
{
SLDataType* a;
int size; //有效数据个数
int capacity; //空间容量
}SL;
2.3 动态顺序表的实现
seqlist.h
seqlist.h
#pragma once
#include<stdlib.h>
#include<stdio.h>
#include<assert.h>
//第一步定义动态顺序表结构
typedef int SLDatatype;
typedef struct SeqList {
SLDatatype* arr;
int capacity;//空间大小
int size; // 有效数据个数
}SL;
//typedef struct SeqList SL;
//初始化
void SLInit(SL *ps);
//销毁
void SLDestroy(SL *ps);
//打印顺序表
void SLPrint(SL*ps);
//插入数据
void SLPushBack(SL*ps,SLDatatype x);//尾插
void SLPushFront(SL*ps,SLDatatype x);//头插
//删除数据
void SLPopBack(SL* ps);//尾删
void SLPopFront(SL* ps);//头删
//在指定位置之前插入数据
void SLInsert(SL*ps,SLDatatype x,int pos);
//删除指定位置的数据
void SLErasee(SL*ps,int pos);
//查找
int SLFind(SL* ps, SLDatatype x);
seqlist.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "SeqList.h"
//初始化
void SLInit(SL *ps)
{
ps->arr = NULL;
ps->capacity = ps->size = 0;
}
//销毁
void SLDestroy(SL *ps)
{
if (ps->arr)//相当于ps->arr!=NULL
{
free(ps->arr);
}
ps->arr = NULL;
ps->capacity = ps->size = 0;
}
void SLcheckCapacity(SL* ps)
{
//判断空间是否充足
if (ps->size == ps->capacity)
{
//如何增容?增容一般是成倍数的增加,比如2.3.......
//增容
//增容//0*2=0;
//若capacity为0,给个默认值,否则*2倍
int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
SLDatatype* tmp = (SLDatatype*)realloc(ps->arr, newCapacity * sizeof(SLDatatype));
if (tmp == NULL)
{
perror("realloc fail!");
exit(1);
}
ps->arr = tmp;
ps->capacity = newCapacity;
}
}
//插入数据
void SLPushBack(SL* ps, SLDatatype x)//尾插
{
assert(ps);//等价于assert(ps!=NULL);
//温柔的解决办法
/*if (ps == NULL)
{
return;
}*/
SLcheckCapacity(ps);
ps->arr[ps->size++] = x;
}
void SLPushFront(SL* ps, SLDatatype x)//头插
{
assert(ps);//等价于assert(ps!=NULL);
SLcheckCapacity(ps);
//数据整体后移
for (int i = ps->size; i > 0; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
//小标为0的数据空出来了
ps->arr[0] = x;
ps->size++;
}
void SLPrint(SL* ps) {
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->arr[i]);
}
printf("\n");
}
//删除数据
void SLPopBack(SL* ps)//尾删
{
assert(ps);
assert(ps->size);
/*ps->arr[ps->size - 1] = -1;*/ //多余了
ps->size--;
}
void SLPopFront(SL* ps)//头删
{
assert(ps);
assert(ps->size);
//将整体数据向前挪动一位
for (int i=0;i<ps->size-1 ; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
//在指定位置之前插入数据
void SLInsert(SL* ps, SLDatatype x, int pos)
{
assert(ps);
assert(pos>=0&&pos<=ps->size);
SLcheckCapacity(ps);
for ( int i = ps->size; i>pos ; i--)
{
ps->arr[i] = ps->arr[i -1];//pos+1 -> pos
}
ps->arr[pos] = x;
ps->size++;
}
//删除指定位置的数据
void SLErasee(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
//还有顺序表不能为空:如顺序表不能为空.....
//pos之后的数据向前挪动一位
for(int i = pos;i<ps->size-1 ; i++)
{
ps->arr[i] = ps->arr[i + 1];//size-2 -> size-1
}
ps->size--;
}
//查找
int SLFind(SL* ps, SLDatatype x)
{
assert(ps);
for ( int i = 0; i < ps->size; i++)
{
if (ps->arr[i] == x)
{
return i;
}
}
//没有找到:返回无效下标
return -1;
}
test .c
test .c
#include"seqList.h"
void SLtest01()
{
SL s;
SLInit(&s);
//SLPushBack(&s,1);//尾插
//SLPushBack(&s,2);//尾插
//SLPushBack(&s,3);//尾插
//SLPushBack(&s,4);//尾插
//SLPushBack(&s,5);//尾插
//SLPushBack(NULL,5);//尾插
SLPushFront(&s, 1);//头插
SLPushFront(&s, 2);//头插
SLPushFront(&s, 3);//头插
SLPushFront(&s, 4);//头插
///*SLPopBack(&s);
//SLPopBack(&s);
//SLPopBack(&s);
//SLPopBack(&s);*/
//SLPopFront(&s);//头删
//SLPopFront(&s);
//SLPopFront(&s);
//SLPopFront(&s);
//SLPopFront(&s);
//SLInsert(&s,11, 0);//删除指定位置前
/*SLErasee(&s, 0)*/;//删除指定位置的数据
int find =SLFind(&s, 2);
if (find < 0)
{
printf("找到了\n");
}
else
printf("没找到\n");
SLPrint(&s);
SLDestroy(&s);
}
int main()
{
SLtest01();
return 0;
}
3. 单链表
3.1 概念与结构
概念:链表是⼀种物理存储结构上⾮连续、⾮顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
单链表结构很像火车eg:
实际的单链表结构:
3.1.1 结点
结点的组成主要有两个部分:当前结点要保存的数据和保存下⼀个结点的地址(指针变量)。
3.1.2链表的性质
1、链式机构在逻辑上是连续的,在物理结构上不⼀定连续。
2、结点⼀般是从堆上申请的。
3、从堆上申请来的空间,是按照⼀定策略分配出来的,每次申请的空间可能连续,可能不连续。
3.1.3 链表的打印
void SLTPrint(SLTNode* phead){
SLTNode*pcur = phead;
while(pcur)
{
printf("%d ",pcur->data);
pcur = pcur->next;
}
printf(\n);
}
3.2 实现单链表
SList.h
SList.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//定义链表(结点)的结构
typedef int SLTDataType;
typedef struct SListNode {
SLTDataType data;
struct SListNode* next;
}SLTNode;
void SLTPrint(SLTNode*phead);
//插入
void SLTPushback(SLTNode**pphead,SLTDataType x);
void SLTPushFront(SLTNode** pphead,SLTDataType x);
//删除
void SLTPopback(SLTNode** pphead);
void SLTPopFront(SLTNode** pphead);
//查找
SLTNode* SLTFind(SLTNode*phead, SLTDataType x);
//在指定位置之前插⼊数据
void SLTInsert(SLTNode * *pphead, SLTNode * pos, SLTDataType x);
//删除pos结点
void SLTErase(SLTNode * *pphead, SLTNode * pos);
//在指定位置之后插⼊数据
void SLTInsertAfter(SLTNode * pos, SLTDataType x);
//删除pos之后的结点
void SLTEraseAfter(SLTNode * pos);
//销毁链表
void SListDestroy(SLTNode * *pphead);
SList.c
SList.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"
//链表的打印
void SLTPrint(SLTNode *phead)
{
SLTNode* pcur = phead;
while (pcur)
{
printf("%d->",pcur->data);
pcur = pcur ->next;
}
printf("NULL\n");
}
//申请新节点
SLTNode* SLTBuyNode(SLTDataType x)
{
SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));
if (node == NULL)
{
perror("malloc fail!");
exit(1);
}
node->data = x;
node->next = NULL;
}
void SLTPushback(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
//申请新节点
SLTNode* newnode = SLTBuyNode( x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
//找尾节点
SLTNode* pcur = *pphead;
while (pcur->next)
{
pcur = pcur->next;
}
//pcur newnode
pcur->next = newnode;
}
}
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = SLTBuyNode( x);
// newnode *pphead
newnode->next = *pphead;
*pphead = newnode;
}
//尾删
void SLTPopback(SLTNode** pphead)
{
//链表为空:不可以删除
assert(pphead &&*pphead);// list 第一个指针
//处理只有一个结点的情况:要删除的就是头结点;
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
//找prev ptail
SLTNode* ptail = *pphead;
SLTNode* prev = NULL;
while (ptail->next)
{
//先让prev指向ptail,再让ptail指向下一个值
prev = ptail;
ptail = ptail->next;
}
prev->next = NULL;
free(ptail);
ptail = NULL;
}
}
//头删
void SLTPopFront(SLTNode** pphead)
{
assert(*pphead && pphead);
SLTNode* next = (*pphead)->next;
//*pphead -->next
free(*pphead); //释放pphead指向的空间
*pphead = next; //让pphead指向 next
}
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
assert(phead);
SLTNode* pcur = phead;
while (pcur)
{
if (pcur->data==x)//对比
{
return pcur;
}
pcur = pcur ->next;
}
//没有找到
return NULL;
}
//在指定位置之前插⼊数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead);
assert(pos);
if (pos == *pphead)
{
SLTPushFront(pphead, x);
}
else
{
SLTNode* newnode = SLTBuyNode(x);
//找prev:pos的前一个结点
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
//prev newnode-->pos
prev->next = newnode;
newnode->next = pos;
}
}
//在指定位置之后插⼊数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = SLTBuyNode(x);
//pos newnode -->pos ->next
newnode->next = pos->next;
pos -> next= newnode;
}
//删除pos结点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead && *pphead);//链表存在
assert(pos);
if (pos == *pphead)
{
SLTPopFront(pphead);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
//prev pos pos->next
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
//删除pos之后的结点
void SLTEraseAfter(SLTNode* pos)
{
assert(pos&&pos->next);//条件:可删
//pos pos->next pos->next->next
SLTNode* del = pos->next;//保存一下pos->next
pos->next = pos ->next->next;
free(del);
del = NULL;
}
void SListDestroy(SLTNode** pphead)
{
assert(pphead && *pphead);//链表存在
SLTNode* pcur = *pphead;
while (pcur)
{
SLTNode* next = pcur->next;
free(pcur);
pcur = next;
}
*pphead = NULL;//pcur已经释放内存,pphead不要忘记释放内存;
}
test.c
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"
//创建一个链表,并打印链表
void createSList()
{
//链表是由一个一个的结点组成
SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));
node1->data = 1;
SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));
node2->data = 2;
SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));
node3->data = 3;
SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));
node4->data = 4;
node1->next = node2;
node2->next = node3;
node3->next = node4;
node4->next = NULL;
//第一个结点的地址作为参数传递过去
SLTNode* plist = node1;
SLTPrint(node1);
}
void SListTest01()
{
SLTNode* plist = NULL;
//尾插
//SLTPushback(&plist, 1);
//SLTPrint(plist);
//SLTPushback(&plist, 2);
//SLTPushback(&plist, 3);
//SLTPrint(plist);
/*SLTPushback(NULL, 4);*/
SLTPushFront(&plist, 1);
SLTPushFront(&plist, 2);
SLTPushFront(&plist, 3);
SLTPushFront(&plist, 4); //4 3 2 1 null
//尾删
/* SLTPopback(&plist);
SLTPopback(&plist);
SLTPopback(&plist);
SLTPopback(&plist);*/
//头删
/*SLTPopFront(&plist);
SLTPopFront(&plist);
SLTPopFront(&plist);*/
SLTNode*find=SLTFind(plist, 2);//用find接收
//if (find == NULL)
//{
// printf("没找到\n");
//}
//else
//{
// printf("找到了\n");
//}
//在指定位置之前插⼊数据
/*SLTInsert(&plist,find,11);*/
//在指定位置之后插⼊数据
/*SLTInsertAfter(find, 11);*/
//删除pos结点
/*SLTErase(&plist, find);*/
/*SLTEraseAfter(find);*/
SListDestroy(&plist);
SLTPrint(plist);
}
int main()
{
/*createSList();*/
SListTest01();
return 0;
}
3.3 链表的分类
链表的结构⾮常多样,以下情况组合起来就有8种(2 x 2 x 2)链表结构:
链表说明:
1.单向或双向
2.带头或不带头
3.循环或者不循环
最常⽤还是两种结构:单链表和双向带头循环链表。
- ⽆头单向⾮循环链表:结构简单,⼀般不会单独⽤来存数据。实际中更多是作为其他数据结构的⼦结构,如哈希桶、图的邻接表等等。
- 带头双向循环链表:结构最复杂,⼀般⽤在单独存储数据。实际中使⽤的链表数据结构,都是带头双向循环链表。
4. 双向链表
4.1 概念与结构
带头双向循环链表
带头链表⾥的头结点,实际为“哨兵位”,哨兵位结点不存储任何有效元素,只是站在这⾥“放哨的”。
4.2 实现双向链表
list.h
list.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
//定义双向链表节点的结构
typedef int LTDataType;
typedef struct ListNode {
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}LTNode;
//为了保持接口一致性,优化接口为一级指针
//初始化
//void LTInit(LTNode** pphead);
LTNode* LTInit();
//销毁
void LTDesTory(LTNode**pphead);
void LTDesTory2(LTNode*phead);//传一级,需要手动将plsit置为Null
//插入
//第一个参传一级还是二级,要看pphead指向的节点会不会发生改变
//要想发生改变,那么pphead的改变要影响实参,传二级
//如果不发生改变,pphead不会影响实参,传一级
//尾插
void LTPpushBack(LTNode* phead, LTDataType x);
//头插(在第一个有效位置之前插入,也就是哨兵位之后)
void LTPpushFront(LTNode* phead, LTDataType x);
//打印链表
void LTPrint(LTNode*phead);
//删除
void LTPopBack(LTNode*phead);
void LTPopFront(LTNode*phead);
//判断是否为空
bool LTEmpty(LTNode*phead);
//查找
LTNode* LTfind(LTNode*phead,LTDataType x);
void LTInsert(LTNode*pos,LTDataType x);
//删除指定位置节点
void LTErase(LTNode*pos);
list.c
list.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "List.h"
//申请空间
LTNode* LTBuyNode(int x) {
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc fail!");
exit(1);
}
newnode->data = x;
//prev next
newnode->next = newnode->prev = newnode;
return newnode;
}
//初始化
//void LTInit(LTNode** pphead)
//{
// //创建头结点(哨兵位)
// *pphead = LTBuyNode(-1);
//}
LTNode* LTInit()
{
LTNode* phead = LTBuyNode(-1);
return phead;
}
//插入
//第一个参传一级还是二级,要看pphead指向的节点会不会发生改变
//要想发生改变,那么pphead的改变要影响实参,传二级
//如果不发生改变,pphead不会影响实参,传一级
void LTPpushBack(LTNode* phead, LTDataType x) {
LTNode* newnode = LTBuyNode(x);
//phead pehad->prev(当前尾节点) newnode
//先从newnode开始,因为这样不影响各个节点
newnode->prev = phead->prev;
newnode->next = phead;
phead->prev = newnode;
phead->prev->next = newnode;
}
//头插(在第一个有效位置之前插入,也就是哨兵位之后)
void LTPpushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = LTBuyNode(x);
//phead phead->next(d1) newnode
newnode->next = phead->next;
newnode->prev = phead;
phead->next = newnode;
newnode->next->prev = newnode;
}
bool LTEmpty(LTNode* phead)
{
assert(phead);
return phead->next == phead;
}
//删除
void LTPopBack(LTNode* phead)
{
assert(phead);
assert(!LTEmpty(phead));
LTNode* del = phead->prev;
LTNode* prev = del->prev;
// phead del(phead->prev) prev
prev->next = phead;
phead->prev = prev;
free(del);
del = NULL;
}
void LTPopFront(LTNode* phead)
{
assert(phead);
assert(!LTEmpty(phead));
LTNode* del = phead->next;
//phead del del->next
phead->next = del->next;
del->next->prev = phead;
free(del);
del = NULL;
}
//查找
LTNode* LTfind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur!=phead)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
//在pos位置之后插入节点
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* newnode = LTBuyNode(x);
// pos newnode pos->next
newnode->next = pos->next;
newnode->prev = pos;
pos->next = newnode;
pos->next->prev = newnode;
}
//删除指定位置节点
void LTErase(LTNode* pos)
{
assert(pos);
//pos->prev pos pos->next
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
free(pos);
pos = NULL;
}
//销毁
void LTDesTory(LTNode**pphead)
{
assert(pphead && *pphead);
LTNode* pcur = (*pphead)->next;
while (pcur != *pphead)
{
LTNode* Next = pcur->next;
free(pcur);
pcur = Next;
}
//销毁哨兵位节点
free(*pphead);
*pphead = NULL;
pcur = NULL;
}
void LTDesTory2(LTNode* phead)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur!=phead)
{
LTNode* Next = pcur->next;
free(pcur);
pcur = Next;
}
free(phead);
phead = pcur = NULL;
}
test.c
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"
void ListTest01()
{
//创建双向链表变量
//LTNode* plist = NULL;
//LTInit(&plist);
LTNode* plist = LTInit();
//插入
/*LTPpushBack(plist, 1);
LTPpushBack(plist, 2);
LTPpushBack(plist, 3);
LTPpushBack(plist, 4);*/
LTPpushFront(plist, 1);
LTPpushFront(plist, 2);
LTPpushFront(plist, 3);
LTPpushFront(plist, 4);
LTPrint(plist);
//删除
/*LTPopBack(plist);
LTPopBack(plist);
LTPopBack(plist);
LTPopBack(plist);
LTPopBack(plist);*/
/*LTPopFront(plist);
LTPopFront(plist);
LTPopFront(plist);
LTPopFront(plist);*/
LTPrint(plist);
LTNode* pos = LTfind(plist, 3);
/*if (pos == NULL)
{
printf("没找到\n");
}
else
{
printf("找到了\n");
}*/
/*LTInsert(pos, 5);*/
LTErase(pos);
LTPrint(plist);
/*LTDesTory(&plist);*/
void LTDesTory2(LTNode * phead);
plist = NULL;
}
//打印链表
void LTPrint(LTNode* phead)
{
LTNode* pcur = phead->next;
while (pcur!=phead)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("\n");
}
int main()
{
ListTest01();
return 0;
}
5. 顺序表与链表的分析
总结
以上就是顺序表和链表的详细内容。喜欢的话可以点赞,收藏文章,激励博主。