一、顺序表
顺序表是一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删改查。
顺序表一般可以分为:
(1)静态顺序表:使用定长数组存储元素
(2)动态顺序表:使用动态开辟的数组存储
功能实现(动态顺序表):
#define MAX_SIZE 3 //最大容量
#define INC_SIZE 10 //扩容增量
typedef int DataType;
typedef struct SeqList
{
DataType* array; //指向动态开辟的数组
int capacity; //容量空间的大小
int size; //有效数据个数
}SeqList;
//初始化顺序表
void SeqListInit(SeqList* s)
{
assert(s != NULL);
s->array = (DataType*)malloc(sizeof(DataType) * MAX_SIZE);
assert(s->array != NULL);
memset(s->array, 0, sizeof(DataType) * MAX_SIZE);
s->capacity = MAX_SIZE;
s->size = 0;
}
//销毁顺序表
void SeqListDestroy(SeqList* s)
{
assert(s != NULL);
if (s->array != NULL)
{
free(s->array);
s->array = NULL;
s->capacity = 0;
s->size = 0;
}
}
//判断顺序表是否为空
bool IsEmpty(SeqList* s)
{
assert(s != NULL);
if (s->size == 0)
return true;
return false;
}
//判断顺序表是否为满
bool IsFull(SeqList* s)
{
assert(s != NULL);
if (s->size == s->capacity)
return true;
return false;
}
//增加顺序表最大容量
bool IncCap(SeqList* s)
{
assert(s != NULL);
DataType* tmp = s->array;
s->array = (DataType*)malloc(sizeof(DataType) * (s->capacity + INC_SIZE));
assert(s->array != NULL);
if (s->array == NULL)
return false;
memcpy(s->array, tmp, sizeof(DataType) * s->size);
free(tmp);
tmp = NULL;
s->capacity += INC_SIZE;
return true;
}
//打印顺序表
void SeqListPrint(SeqList* s)
{
assert(s != NULL);
for (int i = 0; i < s->size; ++i)
printf("%d ", s->array[i]);
printf("\n");
}
//尾插
void SeqListPushBack(SeqList* s, DataType x)
{
assert(s != NULL);
if (IsFull(s) == true)
{
if (IncCap(s) == false)
{
printf("内存不足,顺序表扩容失败\n");
return;
}
}
s->array[s->size] = x;
s->size++;
}
//头插
void SeqListPushFront(SeqList* s, DataType x)
{
assert(s != NULL);
if (IsFull(s) == true)
{
if (IncCap(s) == false)
{
printf("内存不足,顺序表扩容失败\n");
return;
}
}
for (int i = s->size - 1; i >= 0; --i)
s->array[i + 1] = s->array[i];
s->array[0] = x;
s->size++;
}
//头删
void SeqListPopFront(SeqList* s)
{
assert(s != NULL);
if (IsEmpty(s) == false)
{
for (int i = 0; i < s->size - 1; ++i)
s->array[i] = s->array[i + 1];
s->size--;
}
}
//尾删
void SeqListPopBack(SeqList* s)
{
assert(s != NULL);
if (IsEmpty(s) == false)
s->size--;
}
//顺序表查找,找到返回下标,否则返回-1
int SeqListFind(SeqList* s, DataType x)
{
assert(s != NULL);
for (int i = 0; i < s->size; ++i)
if (s->array[i] == x)
return i;
return -1;
}
//顺序表在pos位置插入x
void SeqListInsert(SeqList* s, int pos, DataType x)
{
assert(s != NULL);
if (pos<0 || pos>s->size)
{
printf("pos越界\n");
return;
}
if (IsFull(s) == true)
{
if (IncCap(s) == false)
{
printf("内存不足,顺序表扩容失败\n");
return;
}
}
for (int i = s->size - 1; i >= pos; --i)
s->array[i + 1] = s->array[i];
s->array[pos] = x;
s->size++;
}
//顺序表删除pos位置的值
void SeqListErase(SeqList* s, int pos)
{
assert(s != NULL);
if (pos < 0 || pos >= s->size)
{
printf("pos越界\n");
return;
}
if (IsEmpty(s) == false)
{
for (int i = pos; i < s->size - 1; ++i)
s->array[i] = s->array[i + 1];
s->size--;
}
}
二、链表
链表是一种物理存储结构上非链接、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
注意:
1、从上图可以看出,链式结构在逻辑上是连续的,但是在物理上不一定连续
2、现实中的节点一般都是从堆上申请出来的
3、从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续
链表的分类:
1、单向或者双向
2、带头或者不带头
3、循环或者非循环
常用的两种链表结构:
无头单向非循环链表
带头双向循环链表
1、无头单向非循环链表:结构简单,一般不会单独用来存储数据。实际中更多的是作为其他数据结构的子结构,如哈希桶、图的邻接表等。
2、带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然复杂,但是使用代码实现以后会发现结构会带来很多优势。
链表的实现(无头单向非循环链表):
typedef int DataType;
typedef struct SListNode
{
DataType data;
struct SListNode* next;
}SListNode;
//申请新节点
static SListNode* BuySListNode(DataType x)
{
SListNode* newNode = (SListNode*)malloc(sizeof(SListNode));
if (newNode == NULL)
{
printf("新节点申请失败\n");
exit(0);
}
newNode->data = x;
newNode->next = NULL;
return newNode;
}
//打印链表
void SListNodePrint(SListNode* pList)
{
assert(pList != NULL);
SListNode* pcur = pList;
while (pcur != NULL)
{
printf("%d------>", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
//尾插
void SListNodePushBack(SListNode** ppList, DataType x)
{
assert(ppList != NULL);
//链表为空时
if (*ppList == NULL)
{
*ppList = BuySListNode(x);
}
else
{
//链表不为空
SListNode* pcur = *ppList;
//寻找尾结点
while (pcur->next != NULL)
{
pcur = pcur->next;
}
pcur->next = BuySListNode(x);
}
}
//尾删
void SListNodePopBack(SListNode** ppList)
{
assert(ppList != NULL);
//链表为空
if (*ppList == NULL)
{
return;
}
else if ((*ppList)->next == NULL)
{
//链表只有一个节点
free(*ppList);
*ppList = NULL;
}
else
{
//链表有多个节点
//双指针,pcur寻找尾结点,pre指向pcur的前一个结点
SListNode* pre = *ppList;
SListNode* pcur = (*ppList)->next;
while (pcur->next != NULL)
{
pre = pre->next;
pcur = pcur->next;
}
free(pcur);
pcur = NULL;
pre->next = NULL;
}
}
//头插
void SListNodePushFront(SListNode** ppList, DataType x)
{
assert(ppList != NULL);
/*if ((*ppList) == NULL)
{
//链表为空时
*ppList = BuySListNode(x);
}
else
{
//链表不为空时
SListNode* tmp = *ppList;
*ppList = BuySListNode(x);
(*ppList)->next = tmp;
}*/
//两种情况合并
SListNode* newNode = BuySListNode(x);
newNode->next = *ppList;
*ppList = newNode;
}
//头删
void SListNodePopFront(SListNode** ppList)
{
assert(ppList != NULL);
/*if (*ppList == NULL)
{
//空链表
return;
}
else if ((*ppList)->next == NULL)
{
//链表有一个节点
free(*ppList);
*ppList = NULL;
}
else
{
//链表有多个节点
SListNode* tmp = *ppList;
*ppList = (*ppList)->next;
free(tmp);
tmp = NULL;
}*/
if (*ppList == NULL)
{
return;
}
//两种情况合并
SListNode* DelNode = *ppList;
*ppList = (*ppList)->next;
free(DelNode);
}
//查找
SListNode* SListFind(SListNode* pList, DataType x)
{
assert(pList != NULL);
SListNode* pcur = pList;
while (pcur != NULL)
{
if (pcur->data == x)
return pcur;
pcur = pcur->next;
}
return NULL;
}
//链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, DataType x)
{
if (pos == NULL)
return;
SListNode* tmp = pos->next;
pos->next = BuySListNode(x);
pos->next->next = tmp;
}
//删除链表在pos位置之后的值
void SListEraseAfter(SListNode* pos)
{
if (pos == NULL)
return;
SListNode* tmp = pos->next;
if (tmp == NULL)
{
return;
}
else
{
pos->next = tmp->next;
free(tmp);
tmp = NULL;
}
}
//求链表长度
int SListLength(SListNode* pList)
{
int len = 0;
SListNode* pcur = pList;
while (pcur != NULL)
{
len++;
pcur = pcur->next;
}
return len;
}
//销毁链表
void SListDestroy(SListNode* pList)
{
SListNode* pcur = pList;
while (pcur != NULL)
{
pList = pList->next;
free(pcur);
pcur = pList;
}
}
三、顺序表和链表的区别
不同点 | 顺序表 | 链表 |
存储空间上 | 物理上一定连续 | 逻辑上连续,但物理上不一定连续 |
随机访问 | 支持O(1) | 不支持,O(N) |
任意位置插入或者删除元素 | 可能需要搬移元素,效率低O(N) | 只需要修改指针指向 |
容量 | 动态顺序表,空间不够时需要扩容 | 没有容量的概念 |
应用场景 | 元素高效存储+频繁访问 | 任意位置的插入和删除频繁 |
缓存使用率 | 高 | 低 |