目录
1.链表的分类
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
1.单向或双向
2.带头或不带头
3.循环或非循环
实际中最常用的还是两种结构:
单向-不带头-不循环 —结构最简单 OJ题中常出现/复杂数据结构的子结构(哈希桶、图的临接表)
双向-带头-循环 —结构最复杂 实际中最实用的链表结构(STL--list的结构)
2.实现双向带头循环链表
2.1list.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int LTDataType;
typedef struct List
{
LTDataType data;
struct List* next;
struct List* prev;
}List;
List* BuyListNode(LTDataType x);
List* ListInit();
void ListPrint(List* phead);
void ListPushBack(List* phead, LTDataType x);
void ListPopBack(List* phead);
void ListPushFront(List* phead, LTDataType x);
void ListPopFront(List* phead);
List* ListFind(List* phead, LTDataType x);
void ListInsert(List* pos, LTDataType x);
void ListErase(List* pos);
void ListDestory(List* phead);
2.2List.c
2.2.1尾插
1.创建新节点
2.将新结点与头节点和尾节点链接
//创建新节点
LTNode* BuyLTNode(LTDataType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
printf("malloc fail\n");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
//尾插
void ListPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
//尾节点
LTNode* tail = phead->prev;
//创建新节点
LTNode* newnode = BuyLTNode(x);
//新节点与尾节点链接
tail->next = newnode;
newnode->prev = tail;
//新节点与头节点链接
newnode->next = phead;
phead->prev = newnode;
}
2.2.2初始化
创建一个头节点作为哨兵位,任意赋值,并将其上位和下位都指向自身,形成循环
//初始化
LTNode* ListInit()
{
LTNode* phead = BuyLTNode(0);
phead->next = phead;
phead->prev = phead;
return phead;
}
2.2.3 尾删
1.记录尾节点和尾节点的上一位
2.将尾节点释放并置空,将尾节点的上一位与头节点链接
尾删
void ListPopBack(LTNode* phead)
{
assert(phead);
//检查链表为空
assert(phead->next != phead);
LTNode* tail = phead->prev;
LTNode* tailPrev = tail->prev;
free(tail);
tail = NULL;
tailPrev->next = phead;
phead->prev = tailPrev;
}
2.2.4打印
//打印
void ListPrint(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
2.2.5找x位置
用cur遍历链表,若有节点的data == x,则返回该节点,否则返回NULL。
//寻找x的位置
LTNode* ListFind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
2.2.6在pos位置之前插入x
//在pos位置前插入x
void ListInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* newnode = BuyLTNode(x);
pos->prev->next = newnode;
newnode->prev = pos->prev;
pos->prev = newnode;
newnode->next = pos;
}
2.2.7用ListInsert实现头插、尾插
//头插
void ListPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
ListInsert(phead->next, x);
}
//尾插
void ListPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
ListInsert(phead, x);
}
2.2.8删除pos位置的节点
//删除pos位置的节点
void ListErase(LTNode* pos)
{
assert(pos);
LTNode* prev = pos->prev;
LTNode* next = pos->next;
free(pos);
pos = NULL;
prev->next = next;
next->prev = prev;
}
2.2.9用ListErase实现头删、尾删
//头删
void ListPopFront(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);
ListErase(phead->next);
}
//尾删
void ListPopBack(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);
ListErase(phead->prev);
}
2.2.10销毁链表
//链表销毁
void ListDestory(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
ListErase(cur);
cur = next;
}
free(phead);
}
顺序表
优点:
1.物理空间是连续的,方便下表随机访问。
2.CPU高速缓存命中率更高。
缺点:
1.由于需要物理空间连续,空间不够需要扩容。扩容本身有一定消耗,扩容机制存在一定的空间浪费。
2.头部或者中部插入删除,挪动数据,效率低。
链表
优点:
1.按需申请释放空间。
2.任意位置可以O(1)插入删除数据。
缺点:
1.不支持下标的随机访问。有些算法不适合在上面进行,如二分查找、排序等。