1.线性表
线性表指的是具有部分相同特性的⼀类数据结构的集合,它是由零个或多个数据元素组成的有限序列。
线性表在逻辑上是线性结构,也就说是连续的⼀条直线。但是在物理结构上并不⼀定是连续的, 线性表在物理上存储时,通常以数组和链式结构的形式存储。
常见的线性表:顺序表、链表、栈、队列、字符串......
顺序表是使用一组连续的存储单元依次存储线性表的数据元素。链表则是通过节点将数据元素链接起来,节点包含数据域和指针域,指针域用于指向下一个节点。
2.顺序表
顺序表的底层结构是数组,对数组的封装,实现了常用的增删改查等接口。它使用一组地址连续的存储单元依次存储线性表中的元素。顺序表分为静态顺序表和动态顺序表。
1.静态顺序表
静态顺序表
typedef int Seqdatatype;
#define N 10
typedef struct SeqList
{
Seqdatatype a[N]; //定长数组
int size; //有效数据个数
}sl;
静态顺序表是指采用静态数组来实现的顺序表。它在使用前需要预先分配固定大小的存储空间,一旦分配完成,其容量就不能再改变。
静态顺序表的优点是实现简单,操作直观。
缺点也较为明显:空间大小固定,可能会出现存储空间不足或者浪费的情况。
2.动态顺序表
动态顺序表是在顺序表的基础上,能够根据实际需求动态地调整存储空间大小。它的实现通常基于底层的数组,当现有存储空间不足时,会重新分配一块更大的连续存储空间,并将原数据复制到新的空间中。
动态顺序表的优点在于:
1. 能够更灵活地适应数据规模的变化,避免了因存储空间固定而导致的数据存储受限问题。
2. 相对于链表,它在随机访问上具有更高的效率。
不足之处在于:
1. 动态扩展存储空间时,涉及数据的复制操作,会带来一定的时间开销。
2. 频繁的扩展操作可能导致内存碎片的产生。
在实际应用中,如果需要高效的随机访问,同时数据规模不确定或可能发生较大变化,动态顺序表是一个较好的选择。
typedef struct SeqList
{
Seqdatatype* arr;
int size; //有效数据个数
int capacity; //空间大小
}SL;
下面是动态顺序表的实现:
我们将在三个文件中实现动态顺序表,头文件SeqList.h中实现函数的声明,源文件SeqList中实现函数的定义
text.c :
#include "SeqList.h"
test1()
{
SL sl;
SLInit(&sl);
//尾插
SLPushBack(&sl, 1);
SLPushBack(&sl, 2);
SLPushBack(&sl, 3);
SLPushBack(&sl, 4);
SLPrint(sl);
//头插
SLPushFront(&sl, 9);
SLPushFront(&sl, 8);
SLPushFront(&sl, 7);
SLPrint(sl);
//尾删
SLPopBack(&sl);
SLPopBack(&sl);
SLPrint(sl);
//头删
SLPopFront(&sl);
SLPopFront(&sl);
SLPrint(sl);
//指定位置
SLInsert(&sl, 66, 2);
SLPrint(sl);
//指定删除
SLErase(&sl, 1);
SLPrint(sl);
//查找数据
int find = SLFind(&sl, 66);
if (find < 0)
{
printf("没找到\n");
}
else
{
printf("找到了\n", find);
}
SLDestory(&sl);
}
int main()
{
test1();
return 0;
}
SeqList.h:
#define _CRT_SECURE_NO_WARNINGS
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* data;
int size;
int Capacity;
}SL;
//初始化
void SLInit(SL* ps);
//顺序表的销毁
void SLDestory(SL* ps);
//打印
void SLPrint(SL s);
//判断空间充足
void SLCheckCapacity(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 SLErase(SL* ps, int pos);
//查找数据
int SLFind(SL* ps, SLDataType x);
SeqList.c:
#include "SeqList.h"
//初始化
void SLInit(SL* ps)
{
ps->data = NULL;
ps->size = ps->Capacity = 0;
}
//顺序表的销毁
void SLDestory(SL* ps)
{
if(ps)
{
free(ps->data);
}
ps->data = NULL;
ps->size = ps->Capacity = 0;
}
//打印
void SLPrint(SL s)
{
for(int i = 0;i <s .size;i++)
{
printf("%d ", s.data[i]);
}
printf("\n");
}
//判断空间充足
void SLCheckCapacity(SL* ps)
{
if (ps->size == ps->Capacity)
{
int NewCapacity = ps->Capacity == 0 ? 4 : 2 * ps->Capacity;
SLDataType* tmp = (SLDataType*)realloc(ps->data, sizeof(SLDataType) * NewCapacity);
if (tmp == NULL)
{
perror(tmp);
exit(1);
}
ps->data = tmp;
ps->Capacity = NewCapacity;
}
}
//尾插
void SLPushBack(SL* ps, SLDataType x)
{
assert(ps);
SLCheckCapacity(ps);
//ps->data[ps->size] = x;
//ps->size++;
ps->data[ps->size++] = x;
}
//头插
void SLPushFront(SL* ps, SLDataType x)
{
assert(ps);
SLCheckCapacity(ps);
for (int i = ps->size; i >0; i--)
{
ps->data[i] = ps->data[i - 1];
}
ps->data[0] = x;
ps->size++;
}
//尾删
void SLPopBack(SL* ps)
{
assert(ps);
ps->data[ps->size--];
}
//头删
void SLPopFront(SL* ps)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
ps->data[i] = ps->data[i + 1];
}
ps->size--;
}
//指定位置之前插入数据
void SLInsert(SL* ps, SLDataType x,int pos)
{
assert(ps);
assert(pos > 0 && pos < ps->size);
for (int i = ps->size; i >= ps->size - pos; i--)
{
ps->data[i] = ps->data[i - 1];
}
ps->data[pos - 1] = x;
ps->size++;
}
//指定位置删除数据
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos > 0 && pos < ps->size);
for (int i = 0; i < ps->size - pos; i++)
{
ps->data[pos - 1 + i] = ps->data[pos + i];
}
ps->size--;
}
//查找数据
int SLFind(SL* ps, SLDataType x)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
if (ps->data[i] == x)
{
return i;
}
}
return -1;
}
顺序表的优点在于:
1. 随机访问效率高,能够在 O(1) 的时间复杂度内访问任意位置的元素。
2. 存储密度高,不需要额外的指针空间来存储元素之间的关系。
然而,顺序表也存在一些局限性:
1. 插入和删除操作的效率较低。在插入或删除元素时,可能需要移动大量元素来保持顺序。
2. 预先分配的存储空间可能不够用或造成浪费,难以灵活地动态扩展或收缩存储空间。
在实际应用中,当需要频繁进行随机访问且数据规模相对稳定时,顺序表是一个不错的选择;而当插入和删除操作较为频繁,或者难以预估数据规模时,链表可能更为合适。
3.链表
1.链表的概念和结构
链表是一种常见的数据结构。
链表中的元素称为节点,每个节点包含两部分:数据域和指针域。数据域用于存储节点的数据信息,指针域用于存储指向下一个节点的地址(指针)。
链表分为多种类型,可根据带头不带头,单向双向,循环不循环,组合得到八种。
下面介绍的有单链表(不带头不循环)、双向链表(带头不循环)和循环链表。
单链表中,每个节点的指针域只指向链表中的下一个节点,尾节点的指针域为空(通常用 NULL 或 nullptr 表示)。
双向链表的节点除了有指向下一个节点的指针,还有指向上一个节点的指针,这样可以更方便地实现双向遍历。
循环链表则是尾节点的指针不是指向空,而是指向头节点,形成一个环形结构。
链表的优点在于:
1. 插入和删除操作灵活,不需要像顺序表那样移动大量元素,只需修改相关节点的指针即可。
2. 可以动态地分配内存,适合数据规模不确定的情况。
缺点是:
1. 不支持随机访问,要访问特定位置的节点,需要从头节点开始依次遍历。
2. 每个节点需要额外的指针空间来存储链接信息,导致存储密度相对较低。
2.单链表
在这里介绍的是无头单向非循环链表:结构简单,⼀般不会单独用来存数据。实际中更多是作为其他数据结构的子结构
在单链表中,每个节点包含两部分:数据部分和指针部分。数据部分用于存储具体的数据,指针部分存储下一个节点的地址。
单链表的优点是插入和删除操作的时间复杂度较低,只要修改相关节点的指针即可。缺点是无法随机访问,访问特定节点需要从头指针开始依次遍历。
同样是分成三个文件简单实现单链表
text.c:
#include "SList.h"
test1()
{
SListNode* plist = NULL;
//尾插
SLPushBack(&plist, 1);
SLPushBack(&plist, 2);
SLPushBack(&plist, 3);
SLPushBack(&plist, 4);
SLPrint(plist);
//头插
SLPushFront(&plist, 66);
SLPushFront(&plist, 55);
SLPushFront(&plist, 44);
SLPushFront(&plist, 33);
SLPrint(plist);
//尾删
SLPopBack(&plist);
SLPopBack(&plist);
SLPrint(plist);
//头删
SLPopFront(&plist);
SLPopFront(&plist);
SLPrint(plist);
//int find = SLFind(plist,2);
//if (find > 0)
//{
// printf("找到了");
//}
//else
//{
// printf("没找到");
//}
//指定位置前插后数据
SLPosBack(&plist, 100, 2);
SLPrint(plist);
//指定位置前插入数据
SLPosFront(&plist, 20, 2);
SLPrint(plist);
//删除指定位置的数据
SLErase(&plist, 100);
SLPrint(plist);
//
SLEraseFront(&plist, 100);
SLPrint(plist);
}
int main()
{
test1();
return 0;
}
SList.h:
#define _CRT_SECURE_NO_WARNINGS
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLDataType;
typedef struct SListNode
{
SLDataType data;
struct SListNode* next;
}SListNode;
//打印
void SLPrint(SListNode* phead);
//申请空间
SListNode* SLBuyNode(SLDataType x);
//尾插
void SLPushBack(SListNode** pphead, SLDataType x);
//头插
void SLPushFront(SListNode** pphead, SLDataType x);
//尾删
void SLPopBack(SListNode** pphead);
//头删
void SLPopFront(SListNode** pphead);
//查找数据
int SLFind(SListNode* phead, SLDataType x);
//指定数据之后插入
void SLPosBack(SListNode** pphead, SLDataType x, SLDataType pos);
//指定数据之前插入
void SLPosFront(SListNode** pphead, SLDataType x, SLDataType pos);
//删除指定数据
void SLErase(SListNode** pphead,SLDataType pos);
//删除指定位置之前数据
void SLEraseFront(SListNode** pphead, SLDataType pos);
//销毁链表
void SLDestory(SListNode** pphead);
SList.c:
#include "SList.h"
//打印
void SLPrint(SListNode* phead)
{
assert(phead);
SListNode* pcur = phead;
while (pcur)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("\n");
}
//申请空间
SListNode* SLBuyNode(SLDataType x)
{
SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
if (newnode == NULL)
{
perror(newnode);
exit(1);
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
//尾插
void SLPushBack(SListNode** pphead, SLDataType x)
{
assert(pphead);
SListNode* newnode = SLBuyNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
SListNode* ptail = *pphead;
while (ptail->next)
{
ptail = ptail->next;
}
ptail->next = newnode;
}
}
//头插
void SLPushFront(SListNode** pphead, SLDataType x)
{
assert(pphead);
SListNode* newnode = SLBuyNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
//尾删
void SLPopBack(SListNode** pphead)
{
assert(pphead && *pphead);
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
SListNode* ptail = *pphead;
SListNode* prev = *pphead;
while (ptail->next)
{
prev = ptail;
ptail = ptail->next;
}
free(ptail);
ptail = NULL;
prev->next = NULL;
}
//头删
void SLPopFront(SListNode** pphead)
{
assert(pphead && *pphead);
SListNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
//查找数据
int SLFind(SListNode* phead, SLDataType x)
{
assert(phead);
SListNode* pcur = phead;
while (pcur)
{
if (pcur->data == x)
{
return 1;
}
pcur = pcur->next;
}
return -1;
}
//指定位置之后插入
void SLPosBack(SListNode** pphead, SLDataType x, int pos)
{
assert(pphead && *pphead);
assert(pos);
SListNode* newnode = SLBuyNode(x);
SListNode* pcur = *pphead;
while (pcur != NULL)
{
if (pcur->data == pos)
{
newnode->next = pcur->next;
pcur->next = newnode;
return;
}
pcur = pcur->next;
}
printf("没有找到该数据\n");
}
//指定位置之前插入
void SLPosFront(SListNode** pphead, SLDataType x, int pos)
{
assert(pphead && *pphead);
assert(pos);
if ((*pphead)->data == pos)
{
SLPushFront(pphead, x);
}
else
{
SListNode* newnode = SLBuyNode(x);
SListNode* ptail = *pphead;
SListNode* prev = *pphead;
while (ptail != NULL)
{
if (ptail->data == pos)
{
newnode->next = ptail;
prev->next = newnode;
return;
}
prev = ptail;
ptail = ptail->next;
}
printf("没有找到该数据\n");
}
}
//删除指定位置的数据
void SLErase(SListNode** pphead, SLDataType pos)
{
assert(pphead && *pphead);
assert(pos);
if ((*pphead)->data = pos)
{
SLPopFront(pphead);
}
else
{
SListNode* pcur = *pphead;
SListNode* prev = *pphead;
while (pcur != NULL)
{
if (pcur->data == pos)
{
prev->next = pcur->next;
free(pcur);
pcur = NULL;
return;
}
prev = pcur;
pcur = pcur->next;
}
printf("没有找到该数据");
}
}
//删除指定位置之前数据
void SLEraseFront(SListNode** pphead, SLDataType pos)
{
assert(pphead && *pphead);
assert(pos);
if ((*pphead)->data == pos)
{
return;
}
else
{
SListNode* ptail = *pphead;
SListNode* prev = *pphead;
while (ptail != NULL)
{
if (ptail->next->data == pos)
{
prev->next = ptail->next;
free(ptail);
ptail = NULL;
return;
}
prev = ptail;
ptail = ptail->next;
}
printf("没有该数据");
}
}
//销毁链表
void SLDestory(SListNode** pphead)
{
assert(pphead && *pphead);
SListNode* pcur = *pphead;
while (pcur)
{
SListNode* next = pcur->next;
free(pcur);
pcur = next;
}
*pphead = NULL;
}
3.双链表
双链表(Doubly Linked List)是链表的一种更复杂但功能更强大的形式。
在双链表中,每个节点不仅有指向下一个节点的指针(称为“后继指针”),还有指向上一个节点的指针(称为“前驱指针”)。
双链表的优点在于:
1. 可以从链表的头部和尾部两个方向进行遍历,更加灵活。
2. 在查找某个节点的前驱节点时,无需像单链表那样从头开始遍历,直接通过前驱指针即可快速找到,这使得删除节点等操作更加高效。
然而,双链表也存在一些缺点:
1. 每个节点需要额外存储一个指针,增加了内存开销。
2. 实现的代码相对单链表来说更复杂。
在实际应用中,如果需要频繁地在链表中进行前后双向的遍历和操作,双链表是一个更好的选择;如果对内存开销比较敏感,且操作主要集中在单向遍历和处理上,单链表可能更合适。
text.c:
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include "test.h"
void LT_test1()
{
//初始化
//LTNode* plist = NULL;
//LTInit(&plist);
LTNode* plist = LTInit();
//尾插
LTPushBack(plist, 1);
LTPrint(plist);
LTPushBack(plist, 2);
LTPrint(plist);
LTPushBack(plist, 3);
LTPrint(plist);
//头插
LTPushFront(plist, 5);
LTPrint(plist);
LTPushFront(plist, 6);
LTPrint(plist);
尾删
//LTPopBack(plist);
//LTPrint(plist);
//LTPopBack(plist);
//LTPrint(plist);
头删
//LTPopFront(plist);
//LTPrint(plist);
//LTPopFront(plist);
//LTPrint(plist);
//查找
LTNode* find = LTFind(plist, 3);
if (find == NULL)
{
printf("找不到\n");
}
else
{
printf("找到了\n");
}
//插入数据
LTInsert(find, 10);
find = NULL;
LTPrint(plist);
//删除数据
LTErase(find);
LTPrint(plist);
//
LTDestory(plist);
plist = NULL;
}
int main()
{
LT_test1();
return 0;
}
text.h:
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}LTNode;
//申请节点
LTNode* LTBuyNode(LTDataType x);
//初始化
//void LTInit(LTNode** phead);
LTNode* LTInit();
//打印
void LTPrint(LTNode* phead);
//销毁指针
void LTDestory(LTNode* phead);
//尾插
void LTPushBack(LTNode* phead, LTDataType x);
//头插
void LTPushFront(LTNode* phead, LTDataType x);
//尾删
void LTPopBack(LTNode* phead);
//头删
void LTPopFront(LTNode* phead);
//查找数据
LTNode* LTFind(LTNode* phead, LTDataType x);
//插入数据
void LTInsert(LTNode* pos, LTDataType x);
//删除数据
void LTErase(LTNode* pos);
fun.c:
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include "test.h"
//申请节点
LTNode* LTBuyNode(LTDataType x)
{
LTNode* node = (LTNode*)malloc(sizeof(LTNode));
if (node == NULL)
{
perror("malloc fail");
exit(1);
}
node->data = x;
node->next = node->prev = node;
return node;
}
//初始化
//void LTInit(LTNode** pphead)
//{
// *pphead = LTBuyNode(-1);
//
//}
LTNode* LTInit()
{
LTNode* phead = LTBuyNode(-1);
return phead;
}
//打印
void LTPrint(LTNode* phead)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("\n");
}
//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = LTBuyNode(x);
newnode->prev = phead->prev;
newnode->next = phead;
//LTNode* node = phead->prev;
//node->next = newnode;
phead->prev->next = newnode;
phead->prev = newnode;
}
//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* newnode = LTBuyNode(x);
newnode->prev = phead;
newnode->next = phead->next;
//LTNode* node = phead->next;
//node->prev = newnode;
phead->next->prev = newnode;
phead->next = newnode;
}
//尾删
void LTPopBack(LTNode* phead)
{
assert(phead && phead->next != phead);
LTNode* del = phead->prev;
phead->prev = del->prev;
//LTNode* node = del->prev;
//node->next = phead;
del->prev->next = phead;
free(del);
del = NULL;
}
//头删
void LTPopFront(LTNode* phead)
{
assert(phead && phead->next != phead);
LTNode* del = phead->next;
phead->next = del->next;
//LTNode* node = del->next;
//node->prev = phead;
del->next->prev = phead;
free(del);
del = NULL;
}
//查找数据
LTNode* LTFind(LTNode* phead, LTDataType x)
{
LTNode* pcur = phead->next;
while (pcur != phead)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
//插入数据
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* newnode = LTBuyNode(x);
newnode->next = pos->next;
newnode->prev = pos;
//LTNode* nnode = pos->next;
//nnode->prev = newnode;
pos->next->prev = newnode;
pos->next = newnode;
}
//删除数据
//void LTErase(LTNode* pos)
//{
// assert(pos);
//
// pos->prev->next = pos->next;
// pos->next->prev = pos->prev;
//
// free(pos);
// pos = NULL;
//}
void LTErase(LTNode* pos)
{
assert(pos);
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
free(pos);
pos = NULL;
}
//销毁指针
void LTDestory(LTNode* phead)
{
assert(phead);
LTNode* pcur = phead->next;
while (pcur != phead)
{
LTNode* next = pcur->next;
free(pcur);
pcur = next;
}
free(phead);
phead = NULL;
}