一、链表
1.1 链表的概念以及结构
链表是一种物理上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针依次链接实现的。
逻辑结构是为了方便理解想象出来的,物理结构是实际内存中真实的存储方式。
链表的逻辑结构:

链表的物理结构:

注:链表在逻辑结构上是连续的,但在物理结构上并不一定连续。并且链表的每个节点一般都是用malloc从堆区里申请出来的。
1.2 链表的分类
实际中链表的结构非常多样,不同的组合共有8种:
1.单向或双向

2.带头节点或不带头节点

3.循环或不循环

最常用的两种结构:

1.3 无头单向不循环链表的接口实现
结构:
            
            
              cpp
              
              
            
          
          typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data;		//节点的数据域,用于存放数据
	struct SListNode* next;	//节点的指针域,用于存放指向下一个节点的指针
}SLTNode;		//对结构体的重命名
        链表的打印:

            
            
              cpp
              
              
            
          
          //链表的打印
void SLTPrint(SLTNode* phead)
{
	SLTNode* cur = phead;
	while(cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}
        注:此处的cur是一个临时的结构体指针变量,用于存储结构体的地址,cur=cur->next相当于把cur指向的下一个节点的地址赋给cur这个临时变量,进行访问下一个节点。
动态申请一个节点:
            
            
              cpp
              
              
            
          
          //动态申请一个节点
SLTNode* BuySLTNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}
        链表头插:

            
            
              cpp
              
              
            
          
          void SLTPushFront(SLTNode** pphead, SLTDataType x);
        
            
            
              cpp
              
              
            
          
          //链表头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = BuySLTNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}
        注:链表的头插需要改变的是结构体指针phead,因此在传参时需要传入结构体指针的地址。
链表尾插:

            
            
              cpp
              
              
            
          
          void SLTPushBack(SLTNode** pphead, SLTDataType x);
        
            
            
              cpp
              
              
            
          
          //链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = BuySLTNode(x);
	//链表为空
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		//链表不为空
		SLTNode* tail = *pphead;
		//先找到尾节点
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}
        注:尾插空链表时需要改变的是phead,是一个结构体指针类型,因此传参需要传结构体指针的指针pphead。当尾插的链表不是空链表时,尾插改变的是尾节点的next,改变的是一个结构体类型,只需要结构体指针就行。
链表头删:

            
            
              cpp
              
              
            
          
          void SLTPopFront(SLTNode** pphead);
        
            
            
              cpp
              
              
            
          
          //链表头删
void SLTPopFront(SLTNode** pphead)
{
	assert(pphead);
	//当链表为空,不能继续进行删除操作
	assert(*pphead);
	//链表不为空
	SLTNode* del = *pphead;
	*pphead = del->next;
	free(del);
	del = NULL;
}
        链表尾删:

            
            
              cpp
              
              
            
          
          void SLTPopBack(SLTNode** pphead);
        
            
            
              cpp
              
              
            
          
          //链表尾删
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead);
	//当链表为空,不能继续进行删除操作
	assert(*pphead);
	//当链表只剩下一个节点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		//当链表还剩多个节点
		//先找到尾节点的前一个节点
		SLTNode* prev = *pphead;
		while (prev->next->next != NULL)
		{
			prev = prev->next;
		}
		free(prev->next);
		prev->next = NULL;
	}
}
        链表查找:

            
            
              cpp
              
              
            
          
          SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
        
            
            
              cpp
              
              
            
          
          //链表查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* pos = phead;
	//找到了返回节点的地址,找不到返回NULL
	while (pos)
	{
		if (pos->data != x)
		{
			pos = pos->next;
		}
		else
		{
			return pos;
		}
	}
	return NULL;
}
        链表在pos位置之前插入x:
情况1:pos刚好是第一个节点

情况2:pos不是第一个节点

            
            
              cpp
              
              
            
          
          void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
        
            
            
              cpp
              
              
            
          
          //在pos位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(pos);	//pos不能传空指针
	SLTNode* newnode = BuySLTNode(x);
	//当pos是第一个节点时
	if (*pphead == pos)
	{
		*pphead = newnode;
		newnode->next = pos;
	}
	else
	{
		//当pos不是第一个节点时,需要先找到pos的前一个节点
		SLTNode* cur = *pphead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		cur->next = newnode;
		newnode->next = pos;
	}
}
        链表在pos位置后面插入x:

            
            
              cpp
              
              
            
          
          void SLTInsertAfter(SLTNode* pos, SLTDataType x);
        
            
            
              cpp
              
              
            
          
          //在pos位置后面插入x
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = BuySLTNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}
        注:此处必须先把newnode->next设置为pos->next,如果先把pos->next设置为newnode的话,会出现循环链表的情况。
如下:

链表删除pos位置的值:
情况1:pos刚好是第一个节点

情况2:pos不是第一个节点

            
            
              cpp
              
              
            
          
          void SLTErase(SLTNode** pphead, SLTNode* pos);
        
            
            
              cpp
              
              
            
          
          //删除pos位置的值
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);
	//情况1:如果pos是第一个节点
	if (*pphead == pos)
	{
		*pphead = pos->next;
		free(pos);
		pos = NULL;
	}
	else
	{
		//情况2:pos不是第一个节点
		//先找到pos的前一个节点
		SLTNode* cur = *pphead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		cur->next = pos->next;
		free(pos);
		pos = NULL;
	}
}
        链表删除pos位置后面的值:

            
            
              cpp
              
              
            
          
          void SLTEraseAfter(SLTNode* pos);
        
            
            
              cpp
              
              
            
          
          //删除pos位置后面的值
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);	//pos的下一个值不能为NULL
	SLTNode* del = pos->next;
	pos->next = del->next;
	free(del);
	del = NULL;
}
        单链表的销毁:

            
            
              cpp
              
              
            
          
          void SLTDestroy(SLTNode** pphead);
        
            
            
              cpp
              
              
            
          
          //单链表销毁
void SLTDestroy(SLTNode** pphead)
{
	assert(pphead);
	SLTNode* cur = *pphead;
	while (cur)
	{
		SLTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead = NULL;
}
        1.4 带头双向循环链表的接口实现
带头双向循环链表的结构:

            
            
              cpp
              
              
            
          
          typedef int LTDataType;
typedef struct LTNode
{
	struct LTNode* prev;	//指向前一个节点的指针
	struct LTNode* next;	//指向下一个节点的指针
	LTDataType data;		//用于保持节点的数据
}LTNode;		//对结构体的重命名
        初始化:

            
            
              cpp
              
              
            
          
          LTNode* LTInit();
        
            
            
              cpp
              
              
            
          
          LTNode* LTInit()
{
	LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
	if (phead == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	phead->next = phead;
	phead->prev = phead;
    return phead;
}
        打印:

            
            
              cpp
              
              
            
          
          void LTPrint(LTNode* phead);
        
            
            
              cpp
              
              
            
          
          //打印
void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	printf("哨兵位<==>");
	while (cur != phead)
	{
		printf("%d<==>", cur->data);
		cur = cur->next;
	}
	printf("\n");
}
        申请一个节点:

            
            
              cpp
              
              
            
          
          //申请一个节点
LTNode* LTBuyNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->data = x;
	newnode->next = newnode;
	newnode->prev = newnode;
	return newnode;
}
        尾插:

            
            
              cpp
              
              
            
          
          void LTPushBack(LTNode* phead, LTDataType x);
        
            
            
              cpp
              
              
            
          
          //尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);//双向循环链表必定会有一个头结点head,头结点的指针不可能为空
	LTNode* newnode = LTBuyNode(x);
	//找尾
	LTNode* tail = phead->prev;
	//newnode和尾链接
	tail->next = newnode;
	newnode->prev = tail;
	//newnode和头链接
	newnode->next = phead;
	phead->prev = newnode;
}
        头插:


            
            
              cpp
              
              
            
          
          void LTPushFront(LTNode* phead, LTDataType x);
        
            
            
              cpp
              
              
            
          
          //头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);
	//保存头节点的下一个节点next
	LTNode* next = phead->next;
	//链接头结点和newnode
	phead->next = newnode;
	newnode->prev = phead;
	//链接newnode和next
	newnode->next = next;
	next->prev = newnode;
}
        尾删:

            
            
              cpp
              
              
            
          
          void LTPopBack(LTNode* phead);
        
            
            
              cpp
              
              
            
          
          //尾删
void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);	//链表的有效节点个数不能为0
	//先找到尾节点的前一个节点
	LTNode* tail = phead->prev;
	LTNode* tailPrev = tail->prev;
	//链接tailPrev和phead
	tailPrev->next = phead;
	phead->prev = tailPrev;
	//释放尾节点
	free(tail);
}
        头删:

            
            
              cpp
              
              
            
          
          void LTPopFront(LTNode* phead);
        
            
            
              cpp
              
              
            
          
          //头删
void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);	//链表的有效节点个数不能为0
	//先找第一个节点和第二个节点
	LTNode* first = phead->next;
	LTNode* second = first->next;
	//链接头结点和第二个节点
	phead->next = second;
	second->prev = phead;
	//释放第一个节点
	free(first);
}
        查找:
            
            
              cpp
              
              
            
          
          LTNode* LTFind(LTNode* phead, LTDataType x);
        
            
            
              cpp
              
              
            
          
          //查找
LTNode* LTFind(LTNode* phead,LTDataType x)
{
	assert(phead);
	//找到就返回节点的地址,找不到返回NULL
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}
        注:查找同时也可以代表修改。
在pos位置的前面插入:


            
            
              cpp
              
              
            
          
          void LTInsert(LTNode* pos, LTDataType x);
        
            
            
              cpp
              
              
            
          
          //在pos前面插入
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);	//确保pos不能为空
	//记录pos的前一个节点
	LTNode* posPrev = pos->prev;
	LTNode* newnode = LTBuyNode(x);
	//链接posPrev和newnode
	posPrev->next = newnode;
	newnode->prev = posPrev;
	//链接pos和newnode
	newnode->next = pos;
	pos->prev = newnode;
}
        删除pos位置的值:

            
            
              cpp
              
              
            
          
          void LTErase(LTNode* pos);
        
            
            
              cpp
              
              
            
          
          //删除pos位置的值
void LTErase(LTNode* pos)
{
	assert(pos);
	//保存pos的前一个节点和后一个节点
	LTNode* posPrev = pos->prev;
	LTNode* posNext = pos->next;
	//链接posPrev和posNext
	posPrev->next = posNext;
	posNext->prev = posPrev;
	//释放pos
	free(pos);
}
        注:pos的值也不能等于phead,这一点可以进行另外的判断。
销毁:
            
            
              cpp
              
              
            
          
          void LTDestroy(LTNode* phead);
        
            
            
              cpp
              
              
            
          
          //销毁
void LTDestroy(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
}
        注:销毁需要使用者在外部将phead置空,和free的使用方法相似。
双向带头循环链表的头插、尾插和头删、尾删可以复用Insert和Erase。
二、顺序表和链表的区别
|--------------|---------------------------------------------------------------------------|--------------------|
| 不同点      | 顺序表                                                                   | 链表             |
| 存储空间上        | 一块连续的物理空间                                                                 | 逻辑上连续,但在物理空间上不一定连续 |
| 随机访问         | 支持                                                                        | 不支持                |
| 任意位置的插入或删除元素 | 可能需要挪动元素,效率为O(N)                                                          | 只需要修改指针的指向         |
| 插入           | 动态顺序表空间不够时需要扩容,扩容会有两个缺点1.扩容一般是以2倍的形式进行扩,会有扩容所需要的代价;2.扩容后的插入数据一般还会伴随着空间的浪费 | 按需申请空间             |
| 应用场景         | 元素高效存储+频繁访问                                                               | 频繁在任意位置插入删除        |
| 缓存利用率        | CPU高速缓存的缓存命中率会更高                                                          | CPU高速缓存的缓存命中率会更低   |
有关CPU缓存相关的详细文章: