代码以功能排布,相同的功能,不同种类的链表放在一块便于对比
声明:
#include"stdio.h"
#include"stdlib.h"
typedef struct Node {
int data;
struct Node* Next;
}*PList;
typedef struct DNode {
int data;
struct DNode* Next;
struct DNode* Prior;
}*DList; //双向循环链表
typedef struct CNode {
int data;
struct CNode* Next;
}*CList; //单向循环链表
输出:
不同的链表注意结束的条件
/*输出组*/
void Print(struct Node* p) {
printf("单向链输出: ");
p = p->Next;
while (p) {
printf("%d ", p->data);
p = p->Next;
}
printf("\n");
}
void Print_D(struct DNode* P) {
printf("双向链输出: ");
struct DNode* p = P->Next;
while (p != P) {
printf("%d ", p->data);
p = p->Next;
}
printf("\n");
}
void Print_CH(struct CNode* P) {
printf("循环链输出(头指针): ");
struct CNode* p = P->Next;
while (p != P) {
printf("%d ", p->data);
p = p->Next;
}
printf("\n");
}
void Print_CE(struct CNode* p) { //p指向链表的尾部
printf("循环链输出(尾指针): ");
struct CNode* c = p->Next->Next;
while (c != p->Next) { //c指向头结点的时候结束
printf("%d ", c->data);
c = c->Next;
}
printf("\n");
}
初始化
要对每个指针域赋初始值:
- 单项链表初始时需要对头结点的next赋值NULL
- 循环链表和双向链表的初始化需要next和prior指向头结点
/*初始化组*/
int Init(PList* P) {
*P = (PList)malloc(sizeof(struct Node));
if (!*P) {
printf("failed to initialize\n");
return 1;
}
(*P)->Next = NULL; //一定要记住赋初值
return 0;
}
int DInit(DList* P) {
*P = (DList)malloc(sizeof(struct DNode));
if (!*P) {
printf("failed to initialize\n");
return 1;
}
(*P)->Next = *P;
(*P)->Prior = *P;
return 0;
}
int CInit(CList* P) {
*P = (CList)malloc(sizeof(struct CNode));
if (!*P) {
printf("failed to initialize\n");
return 1;
}
(*P)->Next = *P;
return 0;
}
插入:
注意插入时的p指向的问题:
- p的指向在单链表和循环链表中通常是插入位置的前一个结点
- 在双向链表中,我实现的是指向i的前一个结点,当然也有其他的实现,但其他实现的while的条件就会发生改变
/*插入 从1到n都可插 插到第i个前*/
int ListInsert(PList* P, int i, int e) {
PList p = *P; int j = 0;
while (p && j < i - 1) { //p要指向i前一个结点
p = p->Next; j++;
}
if (i - 1 < j || !p) {
printf("i is error\n");
return 0;
}
PList s = (PList)malloc(sizeof(struct Node));
s->data = e;
s->Next = p->Next;
p->Next = s;
return 1;
}
int DListInsert(DList* P, int i, int e) {
DList p = *P; int j = 0;
while (p->Next != *P && j < i - 1) { //p指向i前一个结点 注意第一次时p指向头结点,需要进入while循环,之后的条件不可以以p指向头结点为退出条件
//因为指向i这个结点时会有麻烦,当在最后一个插入时,需要再次指向头节点,所以while条件就会麻烦。
p = p->Next; j++;
}
if (i - 1 != j) {
printf("i is error\n");
return 0;
}
DList s = (DList)malloc(sizeof(struct DNode));
s->data = e;
s->Next = p->Next;
p->Next->Prior = s;
p->Next = s;
s->Prior = p;
return 1;
}
int CListInsert(CList* P, int i, int e) {
CList p = *P; int j = 0;
while (p->Next != *P && j < i - 1) { //p指向i前一个结点 理由同上
p = p->Next; j++;
}
if (i - 1 != j) {
printf("i is error\n");
return 0;
}
CList s = (CList)malloc(sizeof(struct CNode));
s->data = e;
s->Next = p->Next;
p->Next = s;
return 1;
}
头插法与尾插法:
//头插法
int CHListInsert(CList* P, int e) {
CList H = (CList)malloc(sizeof(struct CNode));
if (!H) {
return 0;
}
H->data = e;
H->Next = (*P)->Next;
(*P)->Next = H;
return 1;
}
//尾插法
int CEListInsert(CList* P, int e) {
CList E = (CList)malloc(sizeof(struct CNode));
if (!E) {
return 0;
}
E->data = e;
E->Next = (*P)->Next;
(*P)->Next = E;
*P = E;
return 1;
}
删除:
删除时p的指向也是个问题:
- 在删除时,单向和循环我实现的都是让p指向 i 前一个结点,双向实现的是指向第i个,你也可以有其他实现
- 删除操作与插入操作的while条件不同,因为删除操作必须要保证删除的结点存在
/*删除组*/
int DeleList(PList* P, int i, int* e) {
PList p = *P; int j = 0; //p指向i前一个结点且要保证第i个结点存在
while (p->Next != NULL && j < i - 1) {
p = p->Next; j++;
}
if (i < j || p->Next == NULL) {
return 0;
}
PList d = p->Next;
*e = d->data;
p->Next = d->Next;
free(d);
return 1;
}
int DeleDList(DList* P, int i, int* e) {
DList p = (*P)->Next; int j = 1; //让p指向第i个结点
while (p != *P && j < i) {
p = p->Next; j++;
}
if (i != j || p == *P) {
return 0;
}
*e = p->data;
p->Next->Prior = p->Prior;
p->Prior->Next = p->Next;
free(p);
return 0;
}
int DeleCList(CList* P, int i, int* e) {
CList p = *P; int j = 0; //p指向i前一个 要保证i个结点存在
while (p->Next != *P && j < i - 1) {
p = p->Next; j++;
}
if (p->Next == *P || i < j) {
return 0;
}
CList d = p->Next;
*e = d->data;
p->Next = d->Next;
}
测试:
你也可以设置一些更刁钻的测试,如果发现输出结果不符合预期,可以在评论区留言
/*测试*/
int main() {
PList plist; DList dlist; CList clist;
Init(&plist); DInit(&dlist); CInit(&clist);
Print(plist);
Print_D(dlist);
Print_CH(clist);
printf("\n");
int elem[] = {1,2,3,4,5};
for (int i = 0; i < 5; i++) { //有效插入
ListInsert(&plist, i+1, elem[i]);
DListInsert(&dlist, i+1, elem[i]);
CListInsert(&clist, i+1, elem[i]);
}
Print(plist);
Print_D(dlist);
Print_CH(clist);
printf("\n");
int err = 9;
//错误插入
ListInsert(&plist, 7, err);
DListInsert(&dlist, 7, err);
CListInsert(&clist, 7, err);
ListInsert(&plist, 0, err);
DListInsert(&dlist, 0, err);
CListInsert(&clist, 0, err);
Print(plist);
Print_D(dlist);
Print_CH(clist);
printf("\n 尾插法: \n");
//循环链表的尾插法
CList c;
CInit(&c);
for (int i = 0; i < 7; i++) {
CEListInsert(&c, i);
}
Print_CE(c);
printf("\n 头插法: \n");
//循环链表的头插法
CList h;
CInit(&h);
for (int i = 0; i < 7; i++) {
CHListInsert(&h, i);
}
Print_CH(h);
printf("\n");
}
这段代码实现了一些链表的基础操作
如果还需要什么功能,可以在评论区内留言