1.线性表
- 概念:是数据结构的一种,一个线性表是n个具有相同特性的数据元素的有限序列。
- 特征:① 集合中必存在唯一的一个“第一元素”。 ② 集合中必存在唯一的“最后元素”。 ③ 除最后一个元素之外,均有唯一的后继(后件)。 ④ 除第一个元素之外,均有唯一的前驱(前件)。
- 常见的线性表:顺序表、链表、栈、队列、字符串…
- 线性表在逻辑上是线性结构,也就是说连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
2.顺序表
-
概念:顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储,在数组上完成数据的增删查改。
-
顺序表一般可以分为:静态顺序表(使用定长数组存储)和动态顺序表(使用动态开辟的数组存储)。
-
顺序表的存储结构
// 顺序表的静态存储
#define N 100
typedef int SDataType;
typedef struct SeqList
{
SDataType arr[N]; // 定长数组
size_t sz; // 有效数据的个数
}SeqList;
// 顺序表的动态存储
typedef int SDataType;
typedef struct SeqList
{
SDataType *arr; // 指向动态开辟的数组
size_t sz; // 有效数据的个数
size_t capacity; // 容量空间的大小
}SeqList;
- 顺序表的动态存储,数组开辟时是动态开辟,没有用定长的,而用指针形式,这样就可以空间不够的时候就增容。
- 顺序表的接口
// 实现顺序表的接口
void SeqListInit(SeqList* ps, size_t capacity); // 顺序表的初始化
void SeqListDestory(SeqList* ps); // 顺序表的销毁
void CheckCapacity(SeqList* ps); // 检查顺序表容量
void SeqListPushBack(SeqList* ps, SDataType x); // 顺序表的尾插
void SeqListPopBack(SeqList* ps); // 顺序表的尾删
void SeqListPushFront(SeqList* ps, SDataType x); // 顺序表的头插
void SeqListPopFront(SeqList* ps); // 顺序表的头删
int SeqListFind(SeqList* ps, SDataType x); // 查找数据元素在顺序表中的索引
void SeqListInsert(SeqList* ps, size_t pos, SDataType x); // 在顺序表特定位置插入特定值
void SeqListErase(SeqList* ps, size_t pos); // 删除顺序表中特定位置的数据元素
void SeqListRemove(SeqList* ps, SDataType x); // 删除顺序表中值为x的元素
void SeqListModify(SeqList* ps, size_t pos, SDataType x); // 将顺序表中固定位置的值改为x
void SeqListPrint(SeqList* ps); // 打印顺序表
void SeqListBubbleSort(SeqList* ps); // 顺序表的冒泡排序
int SeqListBinaryFind(SeqList* ps, SDataType x); // 二分查找顺序表中值为x的索引
void SeqListRemoveAll(SeqList* ps, SDataType x); // 移除顺序表中的所有数据元素
// 顺序表接口的具体实现
void SeqListInit(SeqList* ps, size_t capacity)
{
// 顺序表的初始化
void SeqListDestory(SeqList* ps); // 顺序表的销毁
void CheckCapacity(SeqList* ps); // 检查顺序表容量
void SeqListPushBack(SeqList* ps, SDataType x); // 顺序表的尾插
void SeqListPopBack(SeqList* ps); // 顺序表的尾删
void SeqListPushFront(SeqList* ps, SDataType x); // 顺序表的头插
void SeqListPopFront(SeqList* ps); // 顺序表的头删
int SeqListFind(SeqList* ps, SDataType x); // 查找数据元素在顺序表中的索引
void SeqListInsert(SeqList* ps, size_t pos, SDataType x); // 在顺序表特定位置插入特定值
void SeqListErase(SeqList* ps, size_t pos); // 删除顺序表中特定位置的数据元素
void SeqListRemove(SeqList* ps, SDataType x); // 删除顺序表中值为x的元素
void SeqListModify(SeqList* ps, size_t pos, SDataType x); // 将顺序表中固定位置的值改为x
void SeqListPrint(SeqList* ps); // 打印顺序表
void SeqListBubbleSort(SeqList* ps); // 顺序表的冒泡排序
int SeqListBinaryFind(SeqList* ps, SDataType x); // 二分查找顺序表中值为x的索引
void SeqListRemoveAll(SeqList* ps, SDataType x); // 移除顺序表中的所有数据元素
- 顺序表的接口的实现
// 顺序表接口的实现
// 顺序表的动态存储
typedef int SDataType;
typedef struct SeqList
{
SDataType* array; // 指向动态开辟的数组
size_t size; // 有效数据个数
size_t capicity; // 容量空间的大小
}SeqList;
void SeqListInit(SeqList* ps, size_t capacity)
{
// 顺序表的初始化
assert(ps);
ps->array = (SDataType*)malloc(sizeof(SDataType) * 8);
ps->capicity = capacity;
ps->size = 0;
}
void SeqListDestory(SeqList* ps)
{
// 顺序表的销毁
assert(ps);
if (ps->size > 0)
free(ps->array);
ps->array = NULL;
ps->array = ps->size = 0;
}
void CheckCapacity(SeqList* ps)
{
// 检查顺序表容量
if (ps->size == ps->capicity)
{
int newcapacity = ps->capicity == 0 ? 2 : ps->capicity * 2;
ps->array = (SDataType*)realloc(ps->array, newcapacity * sizeof(SDataType));
ps->capicity = newcapacity;
}
}
void SeqListPushBack(SeqList* ps, SDataType x)
{
// 顺序表的尾插
assert(ps);
CheckCapacity(ps);
ps->array[ps->size] = x;
++ps->size;
}
void SeqListPopBack(SeqList* ps)
{
// 顺序表的尾删
assert(ps);
--ps->size;
}
void SeqListPushFront(SeqList* ps, SDataType x)
{
// 顺序表的头插
assert(ps);
++ps->size;
CheckCapacity(ps);
for (int i = ps->size - 1; i > 0; --i)
{
ps->array[i] = ps->array[i - 1];
}
ps->array[0] = x;
}
void SeqListPopFront(SeqList* ps)
{
// 顺序表的头删
assert(ps);
for (int i = 0; i < ps->size - 1; ++i)
{
ps->array[i] = ps->array[i + 1];
}
--ps->size;
}
int SeqListFind(SeqList* ps, SDataType x)
{
// 查找数据元素在顺序表中的索引
assert(ps);
for (int i = 0; i < ps->size; ++i)
{
if (x == ps->array[i])
return i;
}
return -1;
}
void SeqListInsert(SeqList* ps, size_t pos, SDataType x)
{
// 在顺序表特定位置插入特定值
assert(pos >= 0 && pos <= ps->size - 1);
++ps->size;
CheckCapacity(ps);
for (int i = ps->size - 1; i > pos; --i)
{
ps->array[i] = ps->array[i - 1];
}
ps->array[pos] = x;
}
void SeqListErase(SeqList* ps, size_t pos)
{
// 删除顺序表中特定位置的数据元素
assert(pos >= 0 && pos <= ps->size - 1);
for (int i = pos; i < ps->size; ++i)
{
ps->array[i] = ps->array[i + 1];
}
--ps->size;
}
void SeqListRemove(SeqList* ps, SDataType x)
{
// 删除顺序表中值为x的元素
assert(ps);
/*
int pos = SeqListFind(ps, x);
if (pos != -1)
SeqListErase(ps, pos);
*/
for (int i = 0; i < ps->size; ++i)
{
if (x == ps->array[i])
{
for (int j = i; j < ps->size; ++j)
ps->array[j] = ps->array[j + 1];
--ps->size;
}
}
}
void SeqListModify(SeqList* ps, size_t pos, SDataType x)
{
// 将顺序表中固定位置的值改为x
assert(pos >= 0 && pos <= ps->size - 1);
ps->array[pos] = x;
}
void SeqListPrint(SeqList* ps)
{
// 打印顺序表
for (int i = 0; i < ps->size; ++i)
{
printf("%d ", ps->array[i]);
}
printf("\n");
}
void Swap(int* p, int* q)
{
int tmp = *p;
*p = *q;
*q = tmp;
}
void SeqListBubbleSort(SeqList* ps)
{
// 顺序表的冒泡排序
int end = ps->size;
while (end > 0)
{
for (int i = 1; i < end; ++i)
{
if (ps->array[i - 1] > ps->array[i])
Swap(&ps->array[i - 1], &ps->array[i]);
}
--end;
}
}
int SeqListBinaryFind(SeqList* ps, SDataType x)
{
// 二分查找顺序表中值为x的索引
int left = 0, right = ps->size - 1;
while (left <= right)
{
int mid = left + (right - left) / 2;
if (x > ps->array[mid])
left = mid + 1;
else if (x < ps->array[mid])
right = mid - 1;
else
return mid;
}
return -1;
}
void SeqListRemoveAll(SeqList* ps, SDataType x)
{
// 移除顺序表中的所有值为x的数据元素
assert(ps);
int k = ps->size;
for (int i = 0; i < k; ++i)
{
if (x == ps->array[i])
{
ps->array[i] = ps->array[i + 1];
--ps->size;
}
}
}
2.链表
- 概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
- 链表有8中链表结构:不带头单向非循环链表、带头单向非循环链表、带头单向循环链表、带头单向非循环链表、不带头双向非循环链表、不带头双向循环链表、带头双向非循环链表、带头双向循环链表。
- 虽然链表结构总共有8种,但是常见就只有两种:不带头单向不循环链表和带头双向循环链表。
1.无头单向非循环链表
- 无头单向非循环链表的接口
// 无头单向非循环链表的接口
typedef int SLDataType;
typedef struct SListNode
{
SLDataType _data;
struct SListNode* _next;
}SListNode;
typedef struct Slist
{
SListNode* _head;
}SList;
void SListInit(SList* plst);// 链表的初始化
void SListDestory(SList* plst);// 链表的销毁
SListNode* BuySListNode(SLDataType x);// 建立一个值为x的结点
void SListPushFront(SList* plst, SLDataType x);// 链表的头插
void SListPopFront(SList* plst);// 链表的头删
SListNode* SListFind(SList* plst, SLDataType x);// 在链表中找到值为x的结点
void SListInsertAfter(SListNode* pos, SLDataType x);// 在pos后面插入结点
void SListEraseAfter(SListNode* pos);// 删除链表中pos后的结点
void SListRemove(SList* plst, SLDataType x);// 移除链表中值为x的结点
void SListPrint(SList* plst);// 打印链表
- 无头单向非循环链表接口的实现
// 无头单向非循环链表接口的实现
typedef int SLDataType;
typedef struct SListNode
{
SLDataType _data;
struct SListNode* _next;
}SListNode;
typedef struct Slist
{
SListNode* _head;
}SList;
void SListInit(SList* plst)
{
// 链表的初始化
assert(plst);
plst->_head = NULL;
}
void SListDestory(SList* plst)
{
// 链表的销毁
assert(plst);
SListNode* head = plst->_head;
SListNode* cur = head->_next;
while (cur != NULL)
{
SListNode* next = cur->_next;
free(cur);
cur = next;
}
plst->_head = NULL;
}
SListNode* BuySListNode(SLDataType x)
{
// 建立一个值为x的结点
SListNode* node = (SListNode*)malloc(sizeof(SListNode));
node->_next = NULL;
node->_data = x;
return node;
}
void SListPushFront(SList* plst, SLDataType x)
{
// 链表的头插
assert(plst);
SListNode* tmp = BuySListNode(x);
tmp->_next = plst->_head;
plst->_head = tmp;
}
void SListPopFront(SList* plst)
{
// 链表的头删
aseert(plst);
if (plst->_head == NULL)
{
return;
}
else
{
SListNode* next = plst->_head->_next;
SListNode* cur = plst->_head;
plst->_head = next;
free(cur);
cur = NULL;
}
}
SListNode* SListFind(SList* plst, SLDataType x)
{
// 在链表中找到值为x的结点
assert(plst);
SListNode* cur = plst->_head;
while (cur)
{
if (x == cur->_data)
return cur;
cur = cur->_next;
}
return NULL;
}
void SListInsertAfter(SListNode* pos, SLDataType x)
{
// 在pos后面插入结点
assert(pos);
SListNode* cur = BuySListNode(x);
cur->_next = pos->_next;
pos->_next = cur;
}
void SListEraseAfter(SListNode* pos)
{
// 删除链表中pos后的结点
assert(pos);
if (pos->_next == NULL)
{
return;
}
else
{
SListNode* cur = pos->_next;
pos->_next = pos->_next->_next;
free(cur);
cur = NULL;
}
}
void SListRemove(SList* plst, SLDataType x)
{
// 移除链表中值为x的结点
assert(plst);
SListNode* prev = NULL;
SListNode* cur = plst->_head;
while (cur)
{
if (x == cur->_data)
{
if (prev == NULL)
plst->_head = cur->_next;
else
prev->_next = cur->_next;
free(cur);
cur = NULL;
return;
}
else
{
prev = cur;
cur = cur->_next;
}
}
}
void SListPrint(SList* plst)
{
// 打印链表
assert(plst);
SListNode* cur = plst->_head;
printf("\n");
while (cur)
{
printf("<=> %d ", cur->_data);
cur = cur->_next;
}
printf("\n");
}
2.带头双向循环链表
- 带头双向循环链表的接口
// 带头双向循环链表的接口
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* _prev;
struct ListNode* _next;
LTDataType _data;
}ListNode;
typedef struct List
{
ListNode* _head;
}List;
void ListInit(List* plst);// 链表的初始化
void ListDestory(List* plst);// 链表的销毁
ListNode* ListFind(List* plst, LTDataType x);// 在链表中查找值为x的结点
void ListInsert(ListNode* pos, LTDataType x);// 在链表中插入值为x的结点
void ListErase(ListNode* pos);// 删除pos位置的结点
void ListPushBack(List* plst, LTDataType x);// 尾插
void ListPushFront(List* plst, LTDataType x);// 头插
void ListPopBack(List* plst);// 尾删
void ListPopFront(List* plst);// 头删
void ListPrint(List* plst);// 打印链表
- 带头双向循环链表接口的实现
// 带头双向循环链表接口的实现
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* _prev;
struct ListNode* _next;
LTDataType _data;
}ListNode;
typedef struct List
{
ListNode* _head;
}List;
void ListInit(List* plst)
{
// 链表的初始化
assert(plst);
ListNode* head = (ListNode*)mallloc(sizeof(ListNode));
head->_prev = head;
head->_next = head;
plst->_head = head;
}
void ListDestory(List* plst)
{
// 链表的销毁
assert(plst);
ListNode* head = plst->_head;
ListNode* cur = head->_next;
while (cur)
{
ListNode* next = cur->_next;
free(cur);
cur = next;
}
plst->_head = NULL;
}
ListNode* BuyNode(LTDataType x)
{
ListNode* node = (ListNode*)malloc(sizeof(ListNode));
node->_next = node;
node->_prev = node;
node->_data = x;
return node;
}
ListNode* ListFind(List* plst, LTDataType x)
{
// 在链表中查找值为x的结点
assert(plst);
ListNode* head = plst->_head;
ListNode* cur = head->_next;
while (cur != head)
{
if (x == cur->_data)
return cur;
cur = cur->_next;
}
return NULL;
}
void ListInsert(ListNode* pos, LTDataType x)
{
// 在链表中pos位置前插入值为x的结点
ListNode* prev = pos->_prev;
ListNode* node = BuyNode(x);
prev->_next = node;
node->_prev = prev;
node->_next = pos;
pos->_prev = node;
}
void ListErase(ListNode* pos)
{
// 删除pos位置的结点
ListNode* prev = pos->_prev;
ListNode* next = pos->_next;
free(pos);
prev->_next = next;
next->_prev = prev;
}
void ListPushBack(List* plst, LTDataType x)
{
// 尾插
assert(plst);
/*
ListNode* node = BuyNode(x);
ListNode* head = plst->_head;
ListNode* tail = head->_prev;
tail->_next = node;
node->_prev = tail;
*/
ListInsert(plst->_head, x);
}
void ListPushFront(List* plst, LTDataType x)
{
// 头插
assert(plst);
/*
ListNode* head = plst->_head;
ListNode* node = BuyNode(x);
ListNode* first = head->_next;
head->_next = node;
node->_prev = head;
node->_next = first;
first->_prev = node;
*/
ListInsert(plst->_head->_prev, x);
}
void ListPopBack(List* plst)
{
// 尾删
assert(plst);
/*
ListNode* head = plst->_head;
assert(head->_next != NULL);
ListNode* tail = head->_prev;
ListNode* prev = tail->_prev;
free(tail);
prev->_next = head;
head->_prev = prev;
*/
ListNode* head = plst->_head;
assert(head->_next != NULL);
ListErase(head->_prev);
}
void ListPopFront(List* plst)
{
// 头删
assert(plst);
/*
ListNode* head = plst->_head;
assert(head->_next != NULL);
ListNode* first = head->_next;
ListNode* next = first->_next;
free(first);
head->_next = next;
next->_prev = head;
*/
ListNode* head = plst->_head;
assert(head->_next != NULL);
ListErase(head->_next);
}
void ListPrint(List* plst)
{
// 打印链表
assert(plst);
ListNode* head = plst->_head;
ListNode* cur = head->_next;
printf("head");
while (cur != head)
{
printf("<=> %d ", cur->_data);
cur = cur->_next;
}
printf("\n");
}
3.顺序表和链表的区别和联系
(1.)顺序表:
Ⅰ、 优点:空间连续,支持随机访问,尾插和尾删快。
Ⅱ、 缺点:① 中间或者前面部分的插入删除时间复杂度O(N)即头插和中间插入、删除的效率低。 ② 增容的代价比较大(效率低、浪费空间)。
(2.)带头双向循环链表:
Ⅰ、 缺点:① 任意位置插入删除时间复杂度为O(1)。 ② 没有增容问题,插入一个开辟一个空间,使用一个申请一个,不存在空间浪费。
Ⅱ、 优点:以节点为单位存储,不支持随机访问。