链表基础与操作全解析
链表是什么?
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
指针是什么?
在C++编程中,指针函数是一种特殊的函数,它的返回类型是指针。这意味着,这类函数执行完毕后返回的是一个地址,而不是一个具体的值。指针函数的声明方式通常是在函数返回类型前加上一个星号(*)来表示返回的是一个指针。
在明白了什么是指针和链表后
相信你们都对链表和指针有了一个概念了
链表分为几类?
链表分为四类
1.单链表
创建单链表
使用方法
单链表是一种链式存储的数据结构,每个节点包含数据域和指针域。数据域存储数据,指针域指向下一个节点。单链表的特点是不需要连续的存储空间,适用于频繁插入和删除操作的场景。
单链表的结构定义
首先定义单链表的节点结构:
typedef struct Node {
int data; // 数据域
struct Node *next; // 指针域
} Node, *LinkedList;
初始化单链表
初始化单链表时,需要创建一个头节点,并将其指针域置为空:
void InitList(LinkedList *L) {
*L = (Node *)malloc(sizeof(Node)); // 分配内存
if (*L == NULL) {
printf("内存分配失败\n");
exit(0);
}
(*L)->next = NULL; // 指针域置空
}
头插法建立单链表
头插法是将新节点插入到链表的表头,生成的链表顺序与输入数据顺序相反:
LinkedList HeadInsert(LinkedList L) {
InitList(&L); // 初始化
int x;
scanf("%d", &x);
while (x != 9999) { // 输入9999表示结束
Node *s = (Node *)malloc(sizeof(Node)); // 分配新节点
s->data = x; // 赋值
s->next = L->next; // 插入到表头
L->next = s;
scanf("%d", &x);
}
return L;
}
尾插法建立单链表
尾插法是将新节点插入到链表的表尾,生成的链表顺序与输入数据顺序一致:
LinkedList TailInsert(LinkedList L) {
InitList(&L); // 初始化
Node *r = L; // 尾指针
int x;
scanf("%d", &x);
while (x != 9999) { // 输入9999表示结束
Node *s = (Node *)malloc(sizeof(Node)); // 分配新节点
s->data = x; // 赋值
r->next = s; // 插入到表尾
r = s;
scanf("%d", &x);
}
r->next = NULL; // 尾节点指针域置空
return L;
}
遍历单链表
遍历单链表时,从头节点开始,依次输出每个节点的数据:
void PrintList(LinkedList L) {
Node *p = L->next;
while (p) {
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}
求单链表的长度
计算单链表的长度时,从头节点开始,依次遍历每个节点,计数器加一:
int Length(LinkedList L) {
Node *p = L->next;
int len = 0;
while (p) {
len++;
p = p->next;
}
return len;
}
插入操作
在单链表的第i个位置插入值为x的新节点:
void Insert(LinkedList L, int i, int x) {
Node *p = GetElem(L, i - 1); // 查找第i-1个节点
Node *s = (Node *)malloc(sizeof(Node)); // 分配新节点
s->data = x; // 赋值
s->next = p->next; // 插入节点
p->next = s;
}
删除操作
删除单链表的第i个节点:
void Delete(LinkedList L, int i) {
if (i < 1 || i > Length(L)) {
printf("删除失败:索引错误\n");
return;
}
Node *p = GetElem(L, i - 1); // 查找第i-1个节点
Node *q = p->next; // 被删除节点
p->next = q->next; // 修改指针
free(q); // 释放内存
}
判空操作
判断单链表是否为空,只需检查头节点的指针域是否为空:
bool Empty(LinkedList L) {
if (L->next == NULL) {
printf("链表为空\n");
return true;
} else {
printf("链表不为空\n");
return false;
}
}
通过以上步骤,我们可以创建一个功能完整的单链表,并实现其基本操作
2.双向链表
双向链表是一种常见的线性数据结构,它由一系列节点组成,每个节点包含两个指针,一个指向前一个节点(prev指针),一个指向后一个节点(next指针)。与单链表相比,双向链表具有双向遍历、方便插入和删除、更灵活的操作等优势。
节点定义和初始化
首先,我们需要定义节点的结构体,并实现链表的初始化函数:
typedef int LTDataType;
typedef struct ListNode {
LTDataType _data;
struct ListNode* _next;
struct ListNode* _prev;
} ListNode;
ListNode* ListCreate() {
ListNode* node = (ListNode*)malloc(sizeof(ListNode));
if (!node) {
perror("malloc fail:");
exit(-1);
}
node->_next = node;
node->_prev = node;
return node;
}
插入和删除操作
双向链表的插入和删除操作相对简单,通过修改前后指针,可以方便地调整节点的连接关系。
插入节点
ListNode* BuyNode(LTDataType x) {
ListNode* node = (ListNode*)malloc(sizeof(ListNode));
if (node == NULL) {
perror("malloc fail");
exit(-1);
}
node->_data = x;
node->_next = NULL;
node->_prev = NULL;
return node;
}
void ListInsert(ListNode* pos, LTDataType x) {
assert(pos);
ListNode* newnode = BuyNode(x);
ListNode* dist = pos->_prev;
dist->_next = newnode;
newnode->_prev = dist;
newnode->_next = pos;
pos->_prev = newnode;
}
删除节点
void ListErase(ListNode* pos) {
assert(pos);
ListNode* posPrev = pos->_prev;
ListNode* posNext = pos->_next;
posPrev->_next = posNext;
posNext->_prev = posPrev;
free(pos);
}
其他操作
查找节点
ListNode* ListFind(ListNode* pHead, LTDataType x) {
assert(pHead);
ListNode* cur = pHead->_next;
while (cur != pHead) {
if (cur->_data == x)
return cur;
cur = cur->_next;
}
return NULL;
}
打印链表
void ListPrint(ListNode* pHead) {
ListNode* cur = pHead->_next;
printf("pHead<=>");
while (cur != pHead) {
printf("%d<=>", cur->_data);
cur = cur->_next;
}
printf("\n");
}
销毁链表
void ListDestory(ListNode* pHead) {
assert(pHead);
ListNode* cur = pHead->_next;
while (cur != pHead) {
ListNode* tmp = cur->_next;
free(cur);
cur = tmp;
}
free(pHead);
}
通过以上代码,我们实现了双向链表的基本操作,包括创建、插入、删除、查找、打印和销毁。双向链表在某些特定场景下相比单链表具有更多的优势和灵活性
3.循环链表
循环链表是一种链表数据结构,其中最后一个节点的指针指向第一个节点,形成一个环。这种结构使得从链表中的任何一个节点都可以访问整个链表。在C++中实现循环链表涉及节点的创建、链表的初始化、数据的插入与删除、以及链表的销毁等操作。
循环链表的节点定义
在C++中,循环链表的节点通常定义为一个结构体或类,包含数据域和指向下一个节点的指针域。例如:
struct ListNode {
int data; // 数据域
ListNode* next; // 指向下一个节点的指针
};
初始化循环链表
初始化循环链表时,需要创建一个头节点,并使其指针域指向自身,表示一个空的循环链表。初始化函数可能如下所示:
ListNode* ListInit() {
ListNode* head = new ListNode;
head->next = head; // 指向自己,形成环
head->data = -1; // 可以用特殊值标记头节点
return head;
}
插入数据
循环链表的插入操作与单链表类似,但需要考虑环形结构。插入新节点时,需要先将新节点的next指向当前节点的下一个节点,然后再将当前节点的next指向新节点。插入操作的代码示例:
void ListInsert(ListNode* head, int data, int position) {
ListNode* newNode = new ListNode;
newNode->data = data;
ListNode* current = head;
for (int i = 0; i < position - 1; ++i) {
current = current->next;
}
newNode->next = current->next;
current->next = newNode;
}
删除数据
删除操作需要找到待删除节点的前一个节点,然后将其next指向待删除节点的下一个节点。以下是删除操作的代码示例:
void ListDelete(ListNode* head, int position) {
ListNode* current = head;
for (int i = 0; i < position - 1; ++i) {
current = current->next;
}
ListNode* deleteNode = current->next;
current->next = deleteNode->next;
delete deleteNode;
}
销毁循环链表
销毁循环链表时,需要遍历链表并释放每个节点的内存。最后,将头节点的next指向自身,表示链表已被销毁。销毁函数可能如下所示:
void ListDestroy(ListNode* head) {
ListNode* current = head->next;
while (current != head) {
ListNode* nextNode = current->next;
delete current;
current = nextNode;
}
head->next = head; // 指向自己,表示链表已销毁
}
在实现循环链表时,需要注意处理空链表和非空链表的一致性,确保所有操作都能在环形结构中正确执行。循环链表的实现可以应用于多种场景,如操作系统的任务调度、约瑟夫问题等。通过上述步骤,可以在C++中有效地实现和操作循环链表。
4.静态链表
静态链表是一种线性存储结构,它结合了顺序表和链表的优点。静态链表使用数组存储数据,但数据的位置是随机的,通过一个整形变量(称为“游标”)来维持数据之间的逻辑关系。
存储结构
静态链表的存储结构如下:
struct Component {
ElemType data;
int cur; // 游标,0表示无指向
};
typedef Component StaticLinkList[MAXSIZE];
数组的第一个元素和最后一个元素作为特殊元素处理,不存放数据。第一个元素的cur存放备用链表的第一个结点的下标,最后一个元素的cur存放第一个有数值的元素的下标。
初始化
初始化静态链表,将数组链接成备用链表:
Status InitList(StaticLinkList &space) {
for (int i = 0; i < MAXSIZE - 1; i++)
space[i].cur = i + 1;
space[MAXSIZE - 1].cur = 0; // 静态链表为空
return OK;
}
插入操作
插入新元素前,需要从备用链表中分配空间:
int Malloc_SL(StaticLinkList space) {
int i = space[0].cur;
if (space[0].cur)
space[0].cur = space[i].cur;
return i;
}
插入新元素:
Status Listinsert(StaticLinkList &L, int i, ElemType e) {
int k = MAXSIZE - 1;
if (i < 1 || i > ListLength(L) + 1)
return ERROR;
int j = Malloc_SL(L);
if (j) {
L[j].data = e;
for (int n = 1; n <= i - 1; n++)
k = L[k].cur;
L[j].cur = L[k].cur;
L[k].cur = j;
return OK;
}
return ERROR;
}
删除操作
删除元素时,需要将其空间回收到备用链表中:
void Free_SL(StaticLinkList &space, int k) {
space[k].cur = space[0].cur;
space[0].cur = k;
}
删除元素:
Status ListDelete(StaticLinkList &L, int i) {
int j, k = MAXSIZE - 1;
if (i < 1 || i > ListLength(L))
return ERROR;
for (j = 1; j <= i - 1; j++)
k = L[k].cur;
j = L[k].cur;
L[k].cur = L[j].cur;
Free_SL(L, j);
return OK;
}
示例代码
完整的静态链表实现代码如下:
#include "iostream"
using namespace std;
#define MAXSIZE 1000
#define OK 1
#define ERROR 0
typedef int Status;
typedef int ElemType;
struct Component {
ElemType data;
int cur;
};
typedef Component StaticLinkList[MAXSIZE];
Status InitList(StaticLinkList &space) {
for (int i = 0; i < MAXSIZE - 1; i++)
space[i].cur = i + 1;
space[MAXSIZE - 1].cur = 0;
return OK;
}
int Malloc_SL(StaticLinkList space) {
int i = space[0].cur;
if (space[0].cur)
space[0].cur = space[i].cur;
return i;
}
int ListLength(StaticLinkList L) {
int count = 0;
int i = L[MAXSIZE - 1].cur;
while (i) {
i = L[i].cur;
count++;
}
return count;
}
Status Listinsert(StaticLinkList &L, int i, ElemType e) {
int k = MAXSIZE - 1;
if (i < 1 || i > ListLength(L) + 1)
return ERROR;
int j = Malloc_SL(L);
if (j) {
L[j].data = e;
for (int n = 1; n <= i - 1; n++)
k = L[k].cur;
L[j].cur = L[k].cur;
L[k].cur = j;
return OK;
}
return ERROR;
}
void Free_SL(StaticLinkList &space, int k) {
space[k].cur = space[0].cur;
space[0].cur = k;
}
Status ListDelete(StaticLinkList &L, int i) {
int j, k = MAXSIZE - 1;
if (i < 1 || i > ListLength(L))
return ERROR;
for (j = 1; j <= i - 1; j++)
k = L[k].cur;
j = L[k].cur;
L[k].cur = L[j].cur;
Free_SL(L, j);
return OK;
}
int main() {
StaticLinkList mylist;
InitList(mylist);
int h = Malloc_SL(mylist);
mylist[h].cur = 0;
mylist[h].data = 50;
mylist[999].cur = h;
Listinsert(mylist, 1, 1);
Listinsert(mylist, 2, 2);
Listinsert(mylist, 2, 3);
cout << "遍历并输出该链表上的所有数据:" << endl;
int i = mylist[999].cur;
while (i) {
cout << mylist[i].data << " ";
i = mylist[i].cur;
}
cout << endl;
cout << "please hello world!" << endl;
ListDelete(mylist, 2);
cout << "遍历并输出该链表上的所有数据:" << endl;
i = mylist[999].cur;
while (i) {
cout << mylist[i].data << " ";
i = mylist[i].cur;
}
cout << endl;
system("pause");
return 0;
}