【数据结构与算法】顺序表和链表
创始人
2024-06-01 19:24:50

[数据结构与算法]线性表

  • 线性表
    • 线性表定义:
    • 顺序表
      • 静态顺序表
      • 动态顺序表
    • 动态顺序表的接口实现
    • 链表
      • 链表的概念
      • 链表的分类
    • 单向链表的接口实现
    • 双向链表循环的接口实现
    • 顺序表和链表的区别

线性表

线性表定义:

线性表(linear list)是n个具有相同特性的数据元素的有限序列。

线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串…

线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。

image-20230225163251841

顺序表

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。

顺序表一般可以分为:静态顺序表和动态顺序表

静态顺序表

使用定长数组存储元素:

image-20230225163516671

动态顺序表

使用动态内存开辟的数组:

image-20230225163551447

比较两种顺序表很容易做出选择,静态顺序表由于长度大小固定,必然存在内存不够用或者浪费的问题,所以通常我们讨论的顺序表也是动态表,下面就去实现动态顺序表的增删查改的各个函数接口。

动态顺序表的接口实现

以下是顺序表的接口实现:

typedef int SeqDataType;typedef struct SeqList
{SeqDataType* pc;//存放顺序表地址int size;//有效数据个数int capacity;//顺序表容量
}SeqList;// 基本增删查改接口
// 顺序表初始化
void SeqListInit(SeqList* ps)
{assert(ps);ps->pc = (SeqDataType*)malloc(InitCapacity * sizeof(SeqDataType));if (ps->pc == NULL){perror("SeqListInit::");return;}ps->size = 0;ps->capacity = InitCapacity;
}
// 检查空间,如果满了,进行增容
void SeqListCheckCapacity(SeqList* ps)
{assert(ps);//容量已满if (ps->size == ps->capacity){//扩容两倍SeqDataType* tmp=(SeqDataType*)realloc(ps->pc, (ps->capacity) * sizeof(SeqDataType) * 2);if (tmp == NULL){perror("SeqListCheckCapacity::");return;}//printf("扩容成功\n");//测试ps->pc = tmp;ps->capacity *= 2;/*注意不要忘记调整容量*/}
}
// 顺序表尾插
void SeqListPushBack(SeqList* ps, SeqDataType a)
{assert(ps);//SeqListCheckCapacity(ps);ps->pc[ps->size] = a;ps->size++;//ps->pc[ps->size++] = a;SeqListInsert(ps, ps->size, a);
}
// 顺序表尾删
void SeqListPopBack(SeqList* ps)
{assert(ps);if (ps->size != 0){ps->pc[ps->size - 1] = 0;ps->size--;}else{printf("error\n");//不要忘记判断有效数据size个数}
}
// 顺序表头插
void SeqListPushFront(SeqList* ps,SeqDataType a)
{assert(ps);//SeqListCheckCapacity(ps);//int end = ps->size - 1;//while (end >= 0)//{//	ps->pc[end + 1] = ps->pc[end];//	end--;//}//ps->pc[0] = a;//ps->size++;/*不要忘记size*/SeqListInsert(ps, 0, a);
}
// 顺序表头删
void SeqListPopFront(SeqList* ps)
{assert(ps);int begin = 0;if (ps->size == 0)//判断数据有效{printf("error\n");return;}while (begin < (ps->size - 1)){ps->pc[begin] = ps->pc[begin + 1];begin++;}ps->size--;/*不要忘记size*/
}
// 顺序表查找
int SeqListFind(SeqList* ps, SeqDataType a)
{assert(ps);for (int i = 0; i < ps->size; i++){if (ps->pc[i] == a){return i;}}return -1;
}
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* ps, int pos, SeqDataType a)
{assert(ps);assert(pos >= 0 && pos <= ps->size);SeqListCheckCapacity(ps);int end = ps->size - 1;while (end >= pos){ps->pc[end + 1] = ps->pc[end];end--;}ps->pc[pos] = a;ps->size++;
}
// 顺序表删除pos位置的值
void SeqListErase(SeqList* ps, int pos)
{assert(ps);assert(pos >= 0 && pos < ps->size);int end = ps->size - 1;while (end >= pos){ps->pc[pos] = ps->pc[pos+1];pos++;}ps->size--;
}
// 顺序表销毁
void SeqListDestroy(SeqList* ps)
{assert(ps);ps->size = 0;ps->capacity = 0;free(ps->pc);ps->pc = NULL;
}
// 顺序表打印
void SeqListPrint(SeqList* ps)
{assert(ps);for (int i = 0; i < ps->size; i++){printf("%d ", ps->pc[i]);}
}

链表

链表的概念

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的

image-20230307203917856

链表的分类

链表大致可以分为8种,看到8种虽然不少但是也没必要担心,实际上从笼统的角度来说,我个人更倾向于将链表分为2大类,一种是单链表,另一种是双向链表,其中占大头的是单链表,但是每种链表又可以分为多种(其实也就是在大体逻辑上添加了点东西重新起了一个名字罢了),只要掌握了链表的核心思想这些链表类别其实意义并不是很大,按照思路来写就可以了。

image-20230307211641722

虽然种类有这么多,但是实际上我们最常用也就是最核心的两种:

image-20230307211725051

  1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
  2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。

单向链表的接口实现

单向链表其实就是这个

typedef int SLLDateType;typedef struct SLinkedList
{SLLDateType date;//链表中每个结点的数据struct SLinkedList* next;//下一个结点的地址}SLinkedList;//开一个新结点
SLinkedList* BuyNewNode(SLLDateType x)
{SLinkedList* newnode = (SLinkedList*)malloc(sizeof(SLinkedList));if (newnode == NULL){perror("BuyNewNode::");return NULL;}newnode->date = x;newnode->next = NULL;return newnode;
}//打印
void SLLPrint(SLinkedList* phead)
{SLinkedList* cur = phead;while (cur){printf("%d -> ", cur->date);cur = cur->next;}printf("NULL\n");
}//单链表尾插
void SLLPushBack(SLinkedList** pphead, SLLDateType x)
{SLinkedList* newnode = BuyNewNode(x);//本身链表为空if (*pphead==NULL){*pphead = newnode;}else{//找尾SLinkedList* cur = *pphead;while (cur->next != NULL){cur = cur->next;}cur->next = newnode;}
}//单链表头插
void SLLPushFront(SLinkedList** pphead,SLLDateType x)
{SLinkedList* newnode = BuyNewNode(x);newnode->next = *pphead;*pphead = newnode;}//单链表尾删
void SLLPopBack(SLinkedList** pphead)
{assert(*pphead);//链表为空//单个结点if ((*pphead)->next==NULL){free(*pphead);*pphead = NULL;}else{	//多个结点SLinkedList* cur = *pphead;//SLinkedList* pre = *pphead;//找“尾”//while (cur->next)//{//	pre = cur;//	cur = cur->next;//}//free(cur);//cur = NULL;//pre->next = NULL;/* 第二种写法 */while (cur->next->next){cur = cur->next;}free(cur->next);cur->next = NULL;}
}//单链表头删
void SLLPopFront(SLinkedList** pphead)
{assert(*pphead);SLinkedList* first = *pphead;*pphead = (*pphead)->next;free(first);first = NULL;
}//单链表查找(找到返回位置也相当于修改)
SLinkedList* SLLFind(SLinkedList* plist, SLLDateType x)
{assert(plist);SLinkedList* cur = plist;while (cur){while (cur->date != x){cur = cur->next;}return cur;}return NULL;
}//pos位置后插入
void SLLInsertAfter(SLinkedList* pos, SLLDateType x)
{assert(pos);assert(pos->next);SLinkedList* newnode = BuyNewNode(x);newnode->next = pos->next;pos->next = newnode;}//pos位置后删除
void SLLPopAfter(SLinkedList* pos)
{assert(pos);SLinkedList* del = pos->next;pos->next = pos->next->next;free(del);del = NULL;
}//以下效率不高的两种方式--不推荐
//pos位置前插入
void SLLInsertFront(SLinkedList** pphead, SLinkedList* pos, SLLDateType x)
{assert(pphead);if (pos == *pphead){SLLPushFront(pphead,x);}else{SLinkedList* cur = *pphead;while (cur->next!=pos){cur = cur->next;}SLinkedList* newnode = BuyNewNode(x);newnode->next = cur->next;cur->next = newnode;}
}//pos位置删除
void SLLPopCurrent(SLinkedList** pphead, SLinkedList* pos)
{assert(pphead);assert(pos);assert(*pphead);if (pos == *pphead){SLLPopFront(pphead);}else{SLinkedList* cur = *pphead;while (cur->next != pos){cur = cur->next;}SLinkedList* del = cur->next;cur->next = cur->next->next;free(del);del = NULL;}}

单链表实际上并不适合在某个位置前面插入或删除,效率比较低,所以最后两种写法并不推荐,实际上在C++STL库中也是没有提供前插和前面删除。

image-20230307211300630

双向链表循环的接口实现

一定程序上来说,其实双向链表是比单链表要简单的,例如在删除或者在pos位置前插入等多种情况时,如果是单链表每次都要从头开始去找到前一个节点,这点其实是非常麻烦的,但是双向循环链表则完美的解决了这个问题。

我们要实现的是这种带头双向循环的链表,如图所示:

image-20230311205628801

在单向链表的基础上多一个指针,用来存储前一个位置的地址即可。

直接看接口实现:

typedef int DateType;typedef struct DListNode
{struct DListNode* prev;struct DListNode* next;DateType date;
}ListNode;//开一个新的节点
struct DListNode* BuyNewNode(DateType x)
{ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));if (newnode == NULL){perror("BuyNewNode fail\n");//return NULL;exit(-1);}newnode->date = x;newnode->next = NULL;newnode->prev = NULL;return newnode;
}//开头结点
struct DListNode* DLLInit()
{ListNode* phead = BuyNewNode(-1);phead->next = phead;phead->prev = phead;return phead;
}//检查链表是否为空
bool CheckEmpty(ListNode* head)
{assert(head);/*if (head->next == head)return true;elsereturn false;*/return head->next == head;
}//双向链表打印
void DLLPrint(ListNode* head)
{assert(head);printf("<=head=>");ListNode* cur = head->next;while (cur != head){printf("%d<=>", cur->date);cur = cur->next;}printf("\n");}//双向链表尾插
void DLLPushBack(ListNode* head,DateType x)
{//ListNode* newnode = BuyNewNode(x);//ListNode* tail = head->prev; head      tail  newnode//tail->next = newnode;//newnode->prev = tail;//newnode->next = head;//head->prev = newnode;DLLInsert(head,x);
}//双向链表头插
void DLLPuchFront(ListNode* head, DateType x)
{//ListNode* newnode = BuyNewNode(x);//ListNode* cur = head->next;//cur->prev = newnode;//newnode->next = cur;//newnode->prev = head;//head->next = newnode;DLLInsert(head->next, x);
}//双向链表尾删
void DLLPopBack(ListNode* head)
{assert(head);assert(!CheckEmpty(head));ListNode* tail = head->prev;ListNode* newtail = tail->prev;newtail->next = head;head->prev = newtail;free(tail);tail = NULL;
}//双向链表头删
void DLLPopFront(ListNode* head)
{assert(head);//判断链表是否为空assert(!CheckEmpty(head));ListNode* first = head->next;ListNode* sec = first->next;head->next = sec;sec->prev = head;free(first);first = NULL;
}//双向链表查找
ListNode* DLLFind(ListNode* head, DateType x)
{assert(head);ListNode* cur = head->next;while (cur != head){if (cur->date == x)return cur;cur = cur->next;}return NULL;
}//pos位置插入
void DLLInsert(ListNode* pos, DateType x)
{assert(pos);ListNode* newnode = BuyNewNode(x);ListNode* prev = pos->prev;prev->next = newnode;newnode->next = pos;newnode->prev = prev;pos->prev = newnode;}//pos位置删除
void DLLErase(ListNode* pos)
{assert(pos);ListNode* prev = pos->prev;ListNode* next = pos->next;prev->next = next;next->prev = prev;free(pos);pos = NULL;
}//销毁
void DLLDestroy(ListNode* head)
{assert(head);ListNode* cur = head->next;while (cur != head){ListNode* next = cur->next;free(cur);cur = next;}free(head);
}

顺序表和链表的区别

不同点顺序表链表
存储空间上物理上一定连续逻辑上连续,但物理上不一定连续
随机访问支持O(1)不支持:O(N)
任意位置插入或者删除元素可能需要搬移元素,效率低O(N)只需修改指针指向
插入动态顺序表,空间不够时需要扩容没有容量的概念
应用场景元素高效存储+频繁访问任意位置插入和删除频繁
缓存利用率

image-20230311210411429

相关内容

热门资讯

苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...