线性表:
n(n >= 0)个数据元素组成的一个有限序列,可以在其任意位置上进行插入和删除操作的线性数据结构
从数据在物理内存存储形式上线性表可分为:顺序表和链表
从上图可知:线性表中数据与数据之间存在一对一的关系,即除第一个元素和最后一个元素外,每个元素都有唯一的直接前驱和唯一的直接后继,第一个元素没有前驱,最后一个元素没有后继
顺序表:
用一段地址连续的存储单元依次存储数据元素的线性结构
地址连续的空间,一般情况下采用数组,但数组有静态数组和动态数组, 所以顺序表分为:静态顺序表和动态顺序表
静态顺序表:
结构的定义:
#define MAX_SIZE 10
typedef int DataType;
struct SeqList
{
DataType _array[MAX_SIZE];
int _size; // 顺序表中有效元素的个数
}
动态数据表:
结构的定义:
typedef int DataType;
typedef struct SeqList
{
DataType* _a; //指向顺序表的指针
size_t _size; //当前的有效数据个数
size_t _capicity; //顺序表容量
}SeqList;
动态顺序表的一些基本操作如下:
typedef int DataType;
typedef struct SeqList
{
DataType* _a; //指向顺序表的指针
size_t _size; //当前的有效数据个数
size_t _capicity; //顺序表容量
}SeqList;
void SeqInit(SeqList* pSeq) //线性表初始化
{
assert(pSeq);
pSeq->_a = (DataType*)malloc(sizeof(DataType)*5);
pSeq->_size = 0;
pSeq->_capicity = 10;
}
void SeqDestory(SeqList* pSeq) //线性表销毁
{
assert(pSeq);
free(pSeq->_a);
pSeq->_a = NULL;
pSeq->_size = 0;
pSeq->_capicity = 0;
}
//void SeqPrint(SeqList* pSeq) //测试函数
//{
// assert(pSeq);
//
// for (int i = 0; i < pSeq->_size; ++i)
// printf("%d ",pSeq->_a[i]);
//
// printf("\n");
//}
void SeqPushBack(SeqList* pSeq, DataType x) //顺序表尾插
{
assert(pSeq);
if (pSeq->_size == pSeq->_capicity) //增容判断
{
pSeq->_a = (DataType*)realloc(pSeq->_a, sizeof(DataType)*pSeq->_capicity * 2);
pSeq->_capicity *= 2;
}
pSeq->_a[pSeq->_size++] = x;
}
void SeqPopBack(SeqList* pSeq) //顺序表尾删
{
assert(pSeq);
if (pSeq->_size == 0) //无元素可删
return;
--pSeq->_size;
}
void SeqPushFront(SeqList* pSeq, DataType x) //顺序表头插
{
assert(pSeq);
if (pSeq->_size == pSeq->_capicity) //增容判断
{
pSeq->_a = (DataType*)realloc(pSeq->_a, sizeof(DataType)*pSeq->_capicity * 2);
pSeq->_capicity *= 2;
}
int end = pSeq->_size - 1; //移动所有元素,腾出数组中下标为 0 的空间
while (end >= 0) {
pSeq->_a[end + 1] = pSeq->_a[end];
--end;
}
pSeq->_a[0] = x; //将要插入的元素放到第一个位置处
++pSeq->_size;
}
void SeqPopFront(SeqList* pSeq) //顺序表头删
{
assert(pSeq);
if (pSeq->_size == 0) //无元素可删
return;
--pSeq->_size;
}
void SeqInsert(SeqList* pSeq, size_t pos, DataType x) //指定位置处插入,pos的最小值是1
{
assert(pSeq && pos > 0 && pos <= pSeq->_size+1);
if (pSeq->_size == pSeq->_capicity) //增容判断
{
pSeq->_a = (DataType*)realloc(pSeq->_a, sizeof(DataType)*pSeq->_capicity * 2);
pSeq->_capicity *= 2;
}
int end = pSeq->_size -1; //移动pos位置及后面的所有位置
while (end >= (int)(pos-1)) { //无符号数与有符号数比较时要仔细考虑,有些情况下需强转,否则会导致死循环
pSeq->_a[end + 1] = pSeq->_a[end];
--end;
}
pSeq->_a[pos - 1] = x; //在pos位置插入要插入的元素
++pSeq->_size;
}
void SeqErase(SeqList* pSeq, size_t pos) //指定位置的删除,pos的最小值是1
{
assert(pSeq && pos>0 && pos <= pSeq->_size);
if (pSeq->_size == 0) //无元素可删
return;
int cur = pos - 1; //将pos位置后面的所有元素整体向前移动一个位置单元
while (cur <= pSeq->_size-1){
pSeq->_a[cur] = pSeq->_a[cur+1];
++cur;
}
--pSeq->_size;
}
链表:
一种链式存储的线性表,用一组地址任意的存储单元存放线性表的 数据元素,称存储单元为一个节点
链表的分类:
- 单链表
- 双链表
- 双向顺环链表
以上三种类型的链表又分为有头节点和无头节点两类,所以链表的种类有六种
虽然链表有六种结构类型,我们常用的链表类型是无头单链表和带头的双向循环链表,下面我们只实现这两种类型的链表的一些基本操作
无头节点单链表:
结构的定义:
typedef int DataType;
typedef struct SListNode
{
struct SListNode* _next; //指向下一个节点的指针
DataType _data; //节点中的元素值
}SListNode;
无头节点单链表的一些基本操作:
typedef int DataType;
typedef struct SListNode
{
struct SListNode* _next;
DataType _data;
}SListNode;
SListNode* BuySListNode(DataType x) //构造并初始化一个链表节点
{
SListNode* node = (SListNode*)malloc(sizeof(SListNode));
assert(node);
node->_data = x;
node->_next = NULL;
return node;
}
//void SListPrint(SListNode* pHead) //测试函数
//{
// assert(pHead);
//
// SListNode* cur = pHead;
// while (cur) {
// printf("%d ",cur->_data);
// cur = cur->_next;
// }
//
// printf("\n");
//}
void SListDestory(SListNode** ppHead) //链表的销毁
{
assert(*ppHead);
SListNode* cur = *ppHead;
while (cur) {
SListNode* next = cur->_next; //记录即将要销毁的节点的下一个节点
free(cur);
cur = next;
}
*ppHead = NULL;
}
void SListPushBack(SListNode** ppHead, DataType x) //链表的尾插
{
SListNode* newnode = BuySListNode(x);
if (*ppHead == NULL) //处理一个节点都没有的情况
{
*ppHead = newnode;
return;
}
SListNode* prev = NULL;
SListNode* cur = *ppHead;
while (cur) {
prev = cur; //这个指针的目的是最终指向最有一个节点
cur = cur->_next;
}
prev->_next = newnode;
}
void SListPopBack(SListNode** ppHead) //链表的尾删
{
assert(*ppHead);
if ((*ppHead)->_next == NULL) //处理只有一个节点的情况(即头节点)
{
free(*ppHead);
*ppHead = NULL;
return;
}
SListNode* prev = NULL;
SListNode* cur = *ppHead;
while (cur->_next != NULL) {
prev = cur; //它的目的是记录最后一个节点的前一个节点
cur = cur->_next;
}
free(cur);
cur = NULL;
prev->_next = NULL;
}
void SListPushFront(SListNode** ppHead, DataType x) //链表的头插
{
SListNode* newnode = BuySListNode(x);
if (*ppHead == NULL) //处理一个节点都没有的情况
{
*ppHead = newnode;
return;
}
SListNode* next = *ppHead; //每一次的头插操作都使链表的头指针发生变化
*ppHead = newnode;
newnode->_next = next;
}
void SListPopFront(SListNode** ppHead) //链表的头删
{
assert(*ppHead);
if ((*ppHead)->_next == NULL) //处理只有一个节点的情况(即头节点)
{
free(*ppHead);
*ppHead = NULL;
return;
}
SListNode* next = (*ppHead)->_next; //记录链表的第二个节点
free(*ppHead);
*ppHead = next;
}
SListNode* SListFind(SListNode* pHead, DataType x) //查找
{
assert(pHead);
SListNode* cur = pHead;
while (cur) {
if (cur->_data == x)
return cur;
cur = cur->_next;
}
return NULL;
}
void SListInsest(SListNode** ppHead, SListNode* pos, DataType x) //在pos之前插入
{
assert(pos);
if (pos == *ppHead) //此时就是头插的情况了
{
SListPushFront(ppHead, x);
return;
}
SListNode* newnode = BuySListNode(x);
SListNode* cur = *ppHead;
while (cur) {
if (cur->_next == pos) //最终使cur指向pos的前一个节点
break;
cur = cur->_next;
}
cur->_next = newnode;
newnode->_next = pos;
}
void SListErase(SListNode** ppHead, SListNode* pos) //删除pos指向的节点
{
assert(ppHead && pos);
if (pos == *ppHead) //这种情况就是头删了
{
SListPopFront(ppHead);
return;
}
SListNode* prev = NULL;
SListNode* cur = *ppHead;
while (cur) {
prev = cur; //使prev指向给pos的前一个节点
if (cur->_next == pos)
break;
cur = cur->_next;
}
prev->_next = pos->_next;
free(pos);
pos = NULL;
}
有头节点双向循环链表:
结构的定义:
typedef int DataType;
typedef struct DListNode
{
struct DListNode* _next; //后序节点
struct DListNode* _prev; //前序节点
DataType _data; //节点的数据
}DListNode;
有头节点双向循环链表的一些基本操作:
typedef int DataType;
typedef struct DListNode
{
struct DListNode* _next; //后序节点
struct DListNode* _prev; //前序节点
DataType _data; //节点的数据
}DListNode;
DListNode* BuyDListNode(DataType x) //申请节点并初始化
{
DListNode* node = new DListNode;
assert(node);
node->_data = x;
node->_prev = NULL;
node->_next = NULL;
return node;
}
DListNode* DListInit() //链表的初始化
{
DListNode* pHead = BuyDListNode(0); //这个值随变给
pHead->_prev = pHead;
pHead->_next = pHead;
return pHead;
}
void DListDestory(DListNode* pHead) //链表的销毁
{
assert(pHead);
DListNode* cur = pHead->_next;
DListNode* next = NULL;
while (cur != pHead) {
next = cur->_next;
delete cur;
cur = NULL;
cur = next;
}
free(pHead);
pHead = NULL;
}
//void DListPrint(DListNode* pHead) //测试函数
//{
// assert(pHead);
//
// DListNode* cur = pHead->_next;
// while (cur != pHead) {
// printf("%d ",cur->_data);
//
// cur = cur->_next;
// }
//
// printf("\n");
//}
void DListPushBack(DListNode* pHead, DataType x) //链表的尾插
{
assert(pHead);
DListNode* newnode = BuyDListNode(x);
DListNode* end = pHead->_prev;
end->_next = newnode;
newnode->_prev = end;
newnode->_next = pHead;
pHead->_prev = newnode;
}
void DListPopBack(DListNode* pHead) //链表尾删
{
assert(pHead);
if (pHead->_next == pHead)
return;
DListNode* end = pHead->_prev;
DListNode* prev = end->_prev;
prev->_next = pHead;
pHead->_prev = prev;
delete end;
end = NULL;
}
void DListPushFront(DListNode* pHead, DataType x) //链表头插
{
assert(pHead);
DListNode* newnode = BuyDListNode(x);
DListNode* cur = pHead->_next;
pHead->_next = newnode;
newnode->_prev = pHead;
newnode->_next = cur;
cur->_prev = newnode;
}
void DListPopFront(DListNode* pHead) //链表头删
{
assert(pHead);
if (pHead->_next == pHead)
return;
DListNode* cur = pHead->_next;
DListNode* next = cur->_next;
pHead->_next = next;
next->_prev = pHead;
delete cur;
cur = NULL;
}
DListNode* DListFind(DListNode* pHead, DataType x) //查找
{
assert(pHead);
DListNode* cur = pHead->_next;
while (cur != pHead) {
if (cur->_data == x)
return cur;
cur = cur->_next;
}
return NULL;
}
void DListInsert(DListNode* pHead, DListNode* pos, DataType x) //在pos节点之前插入
{
assert(pHead && pos);
DListNode* newnode = BuyDListNode(x);
DListNode* prev = pos->_prev;
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = pos;
pos->_prev = pos;
}
void DListErase(DListNode* pHead, DListNode* pos) //删除pos指向的节点
{
assert(pHead && pos);
if (pHead->_next == pHead)
return;
DListNode* prev = pos->_prev;
DListNode* next = pos->_next;
prev->_next = next;
next->_prev = prev;
delete pos;
pos = NULL;
}
顺序表与链表的比较:
思考:比较顺序表和链表的优缺点,他们分别在什么场景下使用它?
- 顺序表支持随机访问,单链表不支持随机访问
- 顺序表插入/删除数据效率很低,时间复杂度为O(N)(除尾插尾删),单链表插入/删除效率更高,时间复杂度为O(1)
- 顺序表的CPU高速缓存效率更高,单链表CPU高速缓存效率低
总结一下:
这里认识了所有类型的线性表,其中实现了动态顺序表、无头节点单链表、带头节点的双向循环链表的一些基本操作,我觉得很好理解这里面的逻辑,所以没详细说明。下一篇博客,写写关于链表的经典面试题,到这里我会详细分析每个题目的逻辑,晚安啦~