一、线性表
线性表是n个具有相同特性的数据元素的有限序列。
线性表是一种在实际生活中广泛使用的数据结构,常见的线性表有:顺序表、链表、栈、队列、字符串等。
线性表在逻辑上是线性结构,也就是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
二、顺序表
1.概念
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构。一般情况下采用数组存储,在数组的基础上完成数据的增删查改。
顺序表一般可分为:
①静态顺序表:使用定长数组存储元素
#define CAPACITY 8
typedef int ElemType;
typedef struct SqList
{
ElemType array[CAPACITY];
int size;
}SqList;
②动态顺序表:使用动态开辟的数组存储
typedef int ElemType;
typedef struct SqList
{
ElemType* a;
int size;
int capacity;
}SqList;
2.接口实现
静态顺序表只适用于确切知道需要多少数据的场景,较为局限。现实中基本都是使用动态顺序表,根据需要动态地分配空间大小。下面我们采用动态顺序表。
①初始化
void SqListInit(SqList* p)
{
assert(p);
p->a = NULL;
p->size = 0;
p->capacity = 0;
}
②销毁
void SqListDestroy(SqList* p)
{
assert(p);
free(p->a);
p->a = NULL;
p->capacity = 0;
p->size = 0;
}
③检查容量
如果满了,进行增容
void CheckCapacity(SqList* p)
{
assert(p);
if (p->size == p->capacity)
{
int newcapacity = p->capacity == 0 ? 4 : p->capacity * 2;
ElemType* temp = (ElemType*)realloc(p->a, sizeof(ElemType) * newcapacity);
if (temp == NULL)
{
perror("realloc error:");
return;
}
p->a = temp;
p->capacity = newcapacity;
}
}
④尾插
void PushBack(SqList* p, ElemType x)
{
assert(p);
CheckCapacity(p);
p->a[p->size] = x;
p->size++;
}
⑤头插
void PushFront(SqList* p, ElemType x)
{
assert(p);
CheckCapacity(p);
int end = p->size - 1;
while (end >= 0)
{
p->a[end + 1] = p->a[end];
end--;
}
p->a[0] = x;
p->size++;
}
⑥尾删
void PopBack(SqList* p)
{
assert(p);
assert(p->size > 0);
p->size--;
}
⑦头删
void PopFront(SqList* p)
{
assert(p);
assert(p->size > 0);
for (int i = 1; i < p->size; i++)
{
p->a[i - 1] = p->a[i];
}
p->size--;
}
⑧查找
找到返回下标,找不到返回-1
int SqListFind(SqList* p, ElemType x)
{
assert(p);
for (int i = 0; i < p->size; i++)
{
if (p->a[i] == x)
return i;
}
return -1;
}
⑨指定位置插入
void Insert(SqList* p, int pos, ElemType x)
{
assert(p);
assert(pos >= 0 && pos <= p->size);
CheckCapacity(p);
int end = p->size - 1;
while (end >= pos)
{
p->a[end + 1] = p->a[end];
}
p->a[pos] = x;
p->size++;
}
⑩删除指定位置的元素
void Erase(SqList* p, int pos)
{
assert(p);
assert(pos >= 0 && pos < p->size);
for (int i = pos+1; i < p->size; i++)
{
p->a[i - 1] = p->a[i];
}
p->size--;
}
三、链表
1.概念
链表是一种物理存储结构上非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
在链表的实现中,结点一般都是从堆上申请的,从堆上申请的空间是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续,因此,链式结构在逻辑上是连续的,但在物理上不一定连续。
2.分类
实际中链表的结构非常多样,不同的书籍中结构也不尽相同,但都是按照以下不同的分类角度来组合的:
①单向或双向
单向链表结点只有一个指针域,指向它的下一个结点;双向链表结点有两个指针域,一个指向上一个结点,另一个指向下一个结点。
②带头或不带头
③循环或不循环
虽然组合起来有8种结构之多,但最常用的还是两种结构:
①无头单向不循环链表。该类链表结构简单,常被各种OJ题和面试题所采用。
②带头双向循环链表。该类链表结构是最复杂的,但是在用代码具体实现时该结构会带来很多优势,实际中使用的也大多都是这种结构。
下面也重点展示这两种链表的实现。
3.实现
(1)无头单向不循环链表
结构声明:
typedef int Elemtype;
typedef struct SListNode
{
int val;
struct SListNode* next;
}SListNode;
①创建一个新结点
SListNode* CreateNewNode(Elemtype x)
{
SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
assert(newnode);
newnode->val = x;
newnode->next = NULL;
return newnode;
}
②尾插
void PushBack(SListNode** pp, Elemtype x)
{
assert(pp);
SListNode* newnode = CreateNewNode(x);
if (*pp == NULL)
{
*pp = newnode;
return;
}
else
{
SListNode* tail = *pp;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
return;
}
}
③头插
void PushFront(SListNode** pp, Elemtype x)
{
assert(pp);
SListNode* newnode = CreateNewNode(x);
newnode->next = *pp;
*pp = newnode;
}
④尾删
void PopBack(SListNode** pp)
{
assert(pp);
assert(*pp);
if ((*pp)->next == NULL)
{
free(*pp);
*pp = NULL;
return;
}
else
{
SListNode* pre = *pp;
SListNode* p = pre->next;
while (p->next != NULL)
{
pre = p;
p = p->next;
}
free(p);
pre->next = NULL;
return;
}
}
⑤头删
void PopFront(SListNode** pp)
{
assert(pp);
assert(*pp);
SListNode* temp = *pp;
*pp = (*pp)->next;
free(temp);
}
⑥查找
找到,返回指向该结点的指针;找不到,返回空指针
SListNode* SListFind(SListNode* p, Elemtype x)
{
while (p != NULL)
{
if (p->val == x)
{
return p;
}
p = p->next;
}
return NULL;
}
⑦指定位置插入
void Insert(SListNode** pp, SListNode* pos, Elemtype x)
{
assert(pp);
//要么都为空,要么都不为空(不允许尾插)
assert((!pos && !(*pp)) || (pos && *pp));
if (*pp == pos)
{
//头插
PushFront(pp, x);
}
else
{
SListNode* pre = *pp;
while (pre->next != pos)
{
pre = pre->next;
}
SListNode* newnode = CreateNewNode(x);
pre->next = newnode;
newnode->next = pos;
}
}
⑧指定位置删除
void Erase(SListNode** pp, SListNode* pos)
{
assert(pp && *pp && pos);
if (*pp == pos)
{
*pp = (*pp)->next;
free(pos);
}
else
{
SListNode* pre = *pp;
while (pre->next != pos)
{
pre = pre->next;
}
pre->next = pos->next;
free(pos);
pos = NULL;
}
}
⑨销毁
void Destroy(SListNode** pp)
{
assert(pp);
if (*pp == NULL)
return;
SListNode* pnext = (*pp)->next;
while (pnext != NULL)
{
free(*pp);
*pp = pnext;
pnext = pnext->next;
}
free(*pp);
*pp = NULL;
}
(2)带头双向循环链表
结构声明:
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}ListNode;
①创建并返回链表头结点
ListNode* ListCreate()
{
ListNode* ret = (ListNode*)malloc(sizeof(ListNode));
ret->data = -1;
ret->next = ret;
ret->prev = ret;
return ret;
}
②销毁
void ListDestory(ListNode* pHead)
{
assert(pHead);
ListNode* p = pHead->next;
ListNode* pre = pHead;
while (p != pHead)
{
pre = p;
p = p->next;
free(pre);
}
pHead->next = pHead;
pHead->prev = pHead;
}
注:该函数没有销毁头结点
③尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* tail = pHead->prev;
ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
newNode->data = x;
tail->next = newNode;
newNode->prev = tail;
newNode->next = pHead;
pHead->prev = newNode;
}
④尾删
void ListPopBack(ListNode* pHead)
{
assert(pHead);
if (pHead->next == pHead)
return;
ListNode* pretail = pHead->prev->prev;
free(pretail->next);
pretail->next = pHead;
pHead->prev = pretail;
}
⑤头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
newNode->data = x;
newNode->next = pHead->next;
newNode->next->prev = newNode;
pHead->next = newNode;
newNode->prev = pHead;
}
⑥头删
void ListPopFront(ListNode* pHead)
{
assert(pHead);
if (pHead->next == pHead)
return;
pHead->next = pHead->next->next;
free(pHead->next->prev);
pHead->next->prev = pHead;
}
⑦查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* p = pHead->next;
while (p != pHead)
{
if (p->data == x)
return p;
p = p->next;
}
return NULL;
}
⑧指定位置插入
void ListInsert(ListNode* pos, LTDataType x)
{
assert(pos);
ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
newNode->data = x;
pos->prev->next = newNode;
newNode->prev = pos->prev;
newNode->next = pos;
pos->prev = newNode;
}
⑨指定位置删除
void ListErase(ListNode* pos)
{
assert(pos);
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
free(pos);
}
四、顺序表和链表的比较
不同点 | 顺序表 | 链表 |
存储空间 |
物理上一定连续
|
逻辑上连续,但物理上不一定连续
|
随机访问 | 支持(O(1)) | 不支持 |
任意位置插入或删除元素
|
可能需要搬移元素,效率低O(N)
|
只需修改指针指向
|
插入
|
静态顺序表:空间满了便不能再插入
动态顺序表:空间不够时需要扩容
|
没有容量的概念
|
应用场景
|
元素高效存储
+
频繁访问
|
任意位置插入和删除频繁
|
缓存利用率
| 高 | 低 |