【数据结构笔记】线性表NOTE2——单链表

一 单链表的概念

不同于在内存空间中连续存放线性表的顺序存储方式,链式存储为线性表的另一种存储方式,在链式存储中,表中逻辑上相邻的元素在物理地址空间上不一定相邻,只要元素的指针域中存放的指针指向下一个元素结点即可。
图1

二 单链表的定义

单链表的每个结点都是一个结构体变量LNode,其中数据域变量存储结点数据(这里,以double类型的变量为结点数据的类型),指针域变量为LNode*类型的指针变量。

#include<stdio.h>
#include<stdlib.h>

typedef struct LNode {
	double Data; //数据域
	struct LNode* next; //指针域
}LNode, *LinkList;
//定义单链表的结点

int main(void) {

	LinkList Head = (LinkList)malloc(sizeof(LNode)); 
	//动态分配头结点,并将指向头结点的指针赋值给头指针Head
	if (!Head) {
		printf("Allocation Failure!\n");
		exit(EXIT_FAILURE);
	} //检验存储头结点的内存空间是否分配成功,若失败则退出程序

	return 0;
}

三 单链表的基本操作

与顺序表一样,同为线性表,单链表常见基本操作如下:
①初始化;
②求表长;
③插入操作;
④删除操作;
⑤按位查找;
⑥按值查找;
⑦输出操作;
⑧判空操作;
⑨销毁操作
下面一一讨论上述列举的九种单链表基本操作。

①初始化
我们将初始化操作交由函数InitList完成,而非在main函数中完成。

#include<stdio.h>
#include<stdlib.h>

typedef struct LNode {
	double Data; //数据域
	struct LNode* next; //指针域
}LNode, *LinkList;
//定义单链表的结点

void InitList(LinkList* list) {
	*list = (LinkList)malloc(sizeof(LNode)); 
	//动态分配头结点,并将指向头结点的指针的值赋给头指针
	
	if (!*list) {
		printf("Allocation Failure!\n"); //输出提示分配错误
		exit(EXIT_FAILURE);
	} //若存放头结点的存储空间分配失败,则退出程序

	(*list)->next = NULL; //头结点指针域置空
} 
//初始化链表

注意头指针置空这一步操作不能少,否则(*list)->next将为野指针。

②求表长
沿着指针域中的指针,一个一个结点地移动,每移动一个结点,计数器变量便加1,直到访问到末尾一个结点的空指针域,此时便可将计数器的值作为求表长函数的返回值。

typedef struct LNode {
	double Data; //数据域
	struct LNode* next; //指针域
}LNode, *LinkList;
//定义单链表的结点

int ListLength(LinkList head) {
	LinkList mover = head; 
	//声明在计算表长过程中往前移动的指针
	
	int Counter = 0;
	//表长的计数器,初始值置为0
	
	while (mover->next) {
		mover = mover->next; //向下一个结点的指针域移动
		Counter++; //表长计数器加1
	} //表长计数
	return Counter;
}
//求链表的表长

③插入操作
单链表插入操作示意图如下:
图2
先将指针移动到相应位序,再进行上图所示的插入操作。

#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>

typedef struct LNode {
	double Data; //数据域
	struct LNode* next; //指针域
}LNode, *LinkList;
//定义单链表的结点

bool ListInsert(LinkList head, double value, int Loc) {
	LinkList move = head, insert; 
	//用于在寻找相应位序的结点时移动的指针,及接收新插入的结点的指针
	
	int length = ListLength(head); 
	//声明并计算操作对象链表的长度
	
	if (Loc<1 || Loc>length + 1) {
		printf("Input valid location!\n"); //提示位序非有效位序
		return false;
	} //检查位序参数Loc是否在有效位序范围1~length+1之间
	
	for (int count = 1; count <= Loc - 1; count++) {
		move = move->next;
	} //移动到相应的位序处进行操作
	
	insert = (LinkList)malloc(sizeof(LNode)); 
	//为新插入的结点分配内存空间

	if (!insert) {
		printf("Allocation Failure!\n"); //输出提示分配错误
		exit(EXIT_FAILURE);
	} //若存放新插入的结点的存储空间分配失败,则退出程序
	
	insert->Data = value; //为新结点的数据域赋值
	insert->next = move->next; //新结点指针域指向原先的第Loc个结点
	move->next = insert; //第Loc-1个结点指针域指向新结点
	return true;
} 
//结点插入操作

④删除操作

#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>

typedef struct LNode {
	double Data; //数据域
	struct LNode* next; //指针域
}LNode, *LinkList;
//定义单链表的结点

bool ListDelete(LinkList head, int Loc, double* deleted) {
	LinkList mover = head, reduce; //声明移动的指针,及指向被删除的结点的指针
	int length = ListLength(head); //声明并计算操作对象链表的长度
	if (Loc<1 || Loc>length) {
		printf("Input valid location!\n"); //提示位序非有效位序
		return false;
	} //检查位序参数Loc是否在有效位序范围1~length之间

	for (int count = 1; count <= Loc-1; count++) {
		mover = mover->next;
	} //移动到相应的位序处进行操作
	
	reduce = mover->next; //将指向即将被删除的结点的指针赋给指针变量reduce
	mover->next = reduce->next; //指针移动,指向后一个结点
	*deleted = reduce->Data; //将被删除的结点的数据域的值返回给*deleted
	free(reduce); //释放被删除的结点所占内存空间
	return true;
} 
//结点删除操作

⑤按位查找操作

#include<stdio.h>
#include<stdbool.h>

typedef struct LNode {
	double Data; //数据域
	struct LNode* next; //指针域
}LNode, *LinkList;
//定义单链表的结点

bool GetElem(LinkList head, int Loc, double* Elem) {
	LinkList mover = head; //声明移动的指针
	int length = ListLength(head); //声明并计算操作对象链表的长度
	if (Loc<1 || Loc>length) {
		printf("Input valid location!\n"); //提示位序非有效位序
		return false;
	} //检查位序参数Loc是否在有效位序范围1~length之间

	for (int count = 1; count <= Loc; count++) {
		mover = mover->next;
	} //移动到相应的位序处进行操作

	*Elem = mover->Data; //将第Loc个结点数据域的值赋给*Elem
	return true;
}
//按位查找操作

⑥按值查找操作

#include<stdio.h>
#include<stdbool.h>

typedef struct LNode {
	double Data; //数据域
	struct LNode* next; //指针域
}LNode, *LinkList;
//定义单链表的结点

bool LocateElem(LinkList head, double value, int* result) {
	LinkList mover = head; //声明移动的指针
	int Counter = 0; 
	//计数器初值为0,搜索目标值的过程中,指针mover每往前移动一步,计数器Counter便加1,直到寻找到目标值

	*result = Counter; //结果值*result首先被赋值为初始无效位序0
	
	while (mover->next) {
		mover = mover->next; //若指针域不为空,则指针mover向下一个结点移动
		Counter++; //指针mover每往前移动一步,计数器加1
		if (mover->Data == value) {
			*result = Counter; //查找成功,将目标值第一次出现的位序返回给*result
			return true;
		}
	} //查找操作

	printf("Selection Failure!\n"); //查找失败,屏幕输出失败提示
	return false;
}
//按值查找操作,得到目标值第一次出现时的位序

⑦输出操作

#include<stdio.h>
#include<stdbool.h>

typedef struct LNode {
	double Data; //数据域
	struct LNode* next; //指针域
}LNode, *LinkList;
//定义单链表的结点

void PrintList(LinkList head) {
	LinkList mover = head->next;
	//声明在计算表长过程中往前移动的指针

	while (mover) {
		printf("%.3lf", mover->Data); //指针mover不为空则输出其指向的结点的数据域中存储的值
		if (mover->next) {
			printf(",  "); //格式设定,除最后一个元素外每一个元素输出后都要在后面打个逗号
		}
		mover = mover->next; //向下一个结点的指针域移动
	} //按顺序输出链表各元素
}
//屏幕输出线性表

⑧判空操作
算法非常简单,头结点指针域中存储的指针指向NULL则为空表,反之则不是。

#include<stdbool.h>

typedef struct LNode {
	double Data; //数据域
	struct LNode* next; //指针域
}LNode, *LinkList;
//定义单链表的结点

bool Empty(LinkList head) {
	if (!head->next) {
		return true;
	} //是空表则返回true

	return false; //非空表则返回false
}
//判空操作,若为空表则返回true,反之则返回false

⑨销毁操作

#include<stdlib.h>

typedef struct LNode {
	double Data; //数据域
	struct LNode* next; //指针域
}LNode, *LinkList;
//定义单链表的结点

void DestroyList(LinkList head) {
	LinkList mover = head, destroy = head; 
	//声明用于移动的指针mover,及用于销毁的指针destroy

	while (mover) {
		destroy = mover; //将前一个结点交付给destroy
		mover = mover->next; //指针向后移动
		free(destroy); //前一个结点被销毁
	}
} 
//销毁操作,将链表在内存中所占用的存储空间全部释放

四 自选操作游戏

将上述函数略作改进(增加了对未定义头结点的非法情形的处理),最终可由用户操作的程序如下。

#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>

typedef struct LNode {
	double Data; //数据域
	struct LNode* next; //指针域
}LNode, *LinkList;
//定义单链表的结点

void InitList(LinkList* list); //初始化链表
int ListLength(LinkList head); //求链表的表长

bool ListInsert(LinkList head, double value, int Loc); 
//结点插入操作

bool ListDelete(LinkList head, int Loc, double* deleted); 
//结点删除操作

bool GetElem(LinkList head, int Loc, double* Elem); 
//按位查找操作

bool LocateElem(LinkList head, double value, int* result);
//按值查找操作,得到目标值第一次出现时的位序

bool PrintList(LinkList head); //屏幕输出线性表

bool Empty(LinkList head); //判空操作,若为空表则返回true,反之则返回false

void DestroyList(LinkList* list); //销毁操作,将链表在内存中所占用的存储空间全部释放

void ClearBuffer(void); //清除缓冲区操作








int main(void) {

	LinkList Head = NULL; //声明头指针
	char ch; //用于识别对线性表的操作类型的字符变量
	int loc, selectResult; //用于传参的位序,及按值查找操作所返回的位序

	double element, elementDeleted, elementDetected;
	//用于传参的元素值,删除操作返回的元素值,及按位查找操作所返回的元素值

	printf("There are 8 operations for the list:\n"
		"s: InitList\n"
		"i: ListInsert\n"
		"d: ListDelete\n"
		"l: GetElem\n"
		"v: LocateElem\n"
		"p: PrintList\n"
		"e: Empty\n"
		"h: DestroyList\n"
		"Please input the codes to choose different operations.\n");
	//操作指南

	while (1) {
		printf("Input an operation code: ");
		ch = getchar(); //输入操作所对应的字符

		if (ch == 's') {
			getchar(); //吞掉换行符
			InitList(&Head);
			printf("The sequence list has been successfully initialized.\n");
			//输出提示初始化成功
		}
		//初始化线性表

		else if (ch == 'i') {
			getchar(); //吞掉换行符
			printf("Input the inserted element: ");
			while (scanf("%lf", &element) != 1) {
				printf("Input valid data!\n");
				ClearBuffer(); //吞掉非法输入字符,清除stdin输入缓冲区
			} //排除非法输入
			printf("Input the location: ");
			while (scanf("%d", &loc) != 1) {
				printf("Input valid data!\n");
				ClearBuffer(); //吞掉非法输入字符,清除stdin输入缓冲区
			} //排除非法输入
			getchar(); //吞掉换行符
			if (ListInsert(Head, element, loc)) {
				PrintList(Head);
			}
		}
		//插入操作

		else if (ch == 'd') {
			getchar(); //吞掉换行符
			printf("Input the location of the deleted element: ");
			while (scanf("%d", &loc) != 1) {
				printf("Input valid data!\n");
				ClearBuffer(); //吞掉非法输入字符,清除stdin输入缓冲区
			} //排除非法输入
			getchar(); //吞掉换行符
			if (ListDelete(Head, loc, &elementDeleted)) {
				printf("%.3lf, the No.%d element of the list, has been deleted.\n", elementDeleted, loc);
				PrintList(Head);
			}
		}
		//删除操作

		else if (ch == 'l') {
			getchar(); //吞掉换行符
			printf("Input the location which will be detected: ");
			while (scanf("%d", &loc) != 1) {
				printf("Input valid data!\n");
				ClearBuffer(); //吞掉非法输入字符,清除stdin输入缓冲区
			} //排除非法输入
			getchar(); //吞掉换行符
			if (GetElem(Head, loc, &elementDetected)) {
				printf("The element detected on the location %d is %.3lf!\n", loc, elementDetected);
			}
		}
		//按位查找

		else if (ch == 'v') {
			getchar(); //吞掉换行符
			printf("Input the value which will be selected: ");
			while (scanf("%lf", &element) != 1) {
				printf("Input valid data!\n");
				ClearBuffer(); //吞掉非法输入字符,清除stdin输入缓冲区
			} //排除非法输入
			getchar(); //吞掉换行符
			if (LocateElem(Head, element, &selectResult)) {
				printf("The element %.3lf firstly appeared on the location %d!\n", element, selectResult);
			}
		}
		//按值查找

		else if (ch == 'p') {
			getchar(); //吞掉换行符
			PrintList(Head);
		}
		//输出线性表

		else if (ch == 'e') {
			getchar(); //吞掉换行符
			if (Empty(Head)) {
				printf("The list is null!\n");
			}
			else {
				printf("The list is not null.\n");
			}
		}
		//线性表判空

		else if (ch == 'h') {
			getchar(); //吞掉换行符
			DestroyList(&Head);
			printf("The list has been destroyed!\n");
		}
		//销毁线性表

		else if (ch == '\n') {
		}
		//若从输入缓冲区读取到'\n',则什么也不执行

		else {
			printf("Input valid code!\n");
			ClearBuffer(); //清除stdin输入缓冲区
		}
		//排除非法输入的字符

		printf("\n");
		//为了在格式上区分每一步操作,每一步循环的末尾都要换行
	}

	return 0;
}








void InitList(LinkList* list) {
	if (*list) {
		free(*list);
		printf("The last list has been destroyed!\n");
	} //若原表还未被销毁,则先将原表销毁,以防内存泄漏

	*list = (LinkList)malloc(sizeof(LNode)); 
	//动态分配头结点,并将指向头结点的指针的值赋给头指针
	
	if (!*list) {
		printf("Allocation Failure!\n"); //输出提示分配错误
		exit(EXIT_FAILURE);
	} //若存放头结点的存储空间分配失败,则退出程序

	(*list)->next = NULL; //头结点指针域置空
} 
//初始化链表

int ListLength(LinkList head) {
	LinkList mover = head; 
	//声明在计算表长过程中往前移动的指针
	
	int Counter = 0;
	//表长的计数器,初始值置为0

	if (!mover) {
		printf("Head is NULL!\n");
		return Counter;
	} //头指针为空的处理,此时默认表的长度为0
	
	while (mover->next) {
		mover = mover->next; //向下一个结点的指针域移动
		Counter++; //表长计数器加1
	} //表长计数
	return Counter;
}
//求链表的表长

bool ListInsert(LinkList head, double value, int Loc) {
	LinkList mover = head, insert; 
	//用于在寻找相应位序的结点时移动的指针,及接收新插入的结点的指针

	if (!mover) {
		printf("Head is NULL!\n");
		return false;
	} //头指针为空的处理
	
	int length = ListLength(head); 
	//声明并计算操作对象链表的长度
	
	if (Loc<1 || Loc>length + 1) {
		printf("Input valid location!\n"); //提示位序非有效位序
		return false;
	} //检查位序参数Loc是否在有效位序范围1~length+1之间
	
	for (int count = 1; count <= Loc - 1; count++) {
		mover = mover->next;
	} //移动到相应的位序处进行操作
	
	insert = (LinkList)malloc(sizeof(LNode)); 
	//为新插入的结点分配内存空间

	if (!insert) {
		printf("Allocation Failure!\n"); //输出提示分配错误
		exit(EXIT_FAILURE);
	} //若存放新插入的结点的存储空间分配失败,则退出程序
	
	insert->Data = value; //为新结点的数据域赋值
	insert->next = mover->next; //新结点指针域指向原先的第Loc个结点
	mover->next = insert; //第Loc-1个结点指针域指向新结点
	return true;
} 
//结点插入操作

bool ListDelete(LinkList head, int Loc, double* deleted) {
	LinkList mover = head, reduce; //声明移动的指针,及指向被删除的结点的指针
	if (!mover) {
		printf("Head is NULL!\n");
		return false;
	} //头指针为空的处理

	int length = ListLength(head); //声明并计算操作对象链表的长度
	if (Loc<1 || Loc>length) {
		printf("Input valid location!\n"); //提示位序非有效位序
		return false;
	} //检查位序参数Loc是否在有效位序范围1~length之间

	for (int count = 1; count <= Loc-1; count++) {
		mover = mover->next;
	} //移动到相应的位序处进行操作

	reduce = mover->next; //将指向即将被删除的结点的指针赋给指针变量reduce
	mover->next = reduce->next; //指针移动,指向后一个结点
	*deleted = reduce->Data; //将被删除的结点的数据域的值返回给*deleted
	free(reduce); //释放被删除的结点所占内存空间
	return true;
} 
//结点删除操作

bool GetElem(LinkList head, int Loc, double* Elem) {
	LinkList mover = head; //声明移动的指针
	if (!mover) {
		printf("Head is NULL!\n");
		return false;
	} //头指针为空的处理

	int length = ListLength(head); //声明并计算操作对象链表的长度
	if (Loc<1 || Loc>length) {
		printf("Input valid location!\n"); //提示位序非有效位序
		return false;
	} //检查位序参数Loc是否在有效位序范围1~length之间

	for (int count = 1; count <= Loc; count++) {
		mover = mover->next;
	} //移动到相应的位序处进行操作

	*Elem = mover->Data; //将第Loc个结点数据域的值赋给*Elem
	return true;
}
//按位查找操作

bool LocateElem(LinkList head, double value, int* result) {
	LinkList mover = head; //声明移动的指针

	if (!mover) {
		printf("Head is NULL!\n");
		return false;
	} //头指针为空的处理

	int Counter = 0; 
	//计数器初值为0,搜索目标值的过程中,指针mover每往前移动一步,计数器Counter便加1,直到寻找到目标值

	*result = Counter; //结果值*result首先被赋值为初始无效位序0
	
	while (mover->next) {
		mover = mover->next; //若指针域不为空,则指针mover向下一个结点移动
		Counter++; //指针mover每往前移动一步,计数器加1
		if (mover->Data == value) {
			*result = Counter; //查找成功,将目标值第一次出现的位序返回给*result
			return true;
		}
	} //查找操作

	printf("Selection Failure!\n"); //查找失败,屏幕输出失败提示
	return false;
}
//按值查找操作,得到目标值第一次出现时的位序

bool PrintList(LinkList head) {
	if (!head) {
		printf("Head is NULL!\n");
		return false;
	} //头指针为空的处理

	LinkList mover = head->next;
	//声明在计算表长过程中往前移动的指针

	while (mover) {
		printf("%.3lf", mover->Data); //指针mover不为空则输出其指向的结点的数据域中存储的值
		if (mover->next) {
			printf(",  "); //格式设定,除最后一个元素外每一个元素输出后都要在后面打个逗号
		}
		mover = mover->next; //向下一个结点的指针域移动
	} //按顺序输出链表各元素
	
	return true;
}
//屏幕输出线性表

bool Empty(LinkList head) {
	if (!head) {
		return true;
	}
	else if (!head->next) {
		return true;
	} //是空表则返回true(只有头结点,或连头指针都为空)

	return false; //非空表则返回false
}
//判空操作,若为空表则返回true,反之则返回false

void DestroyList(LinkList* list) {
	LinkList mover = *list, destroy = *list;
	//声明用于移动的指针mover,及用于销毁的指针destroy

	while (mover) {
		destroy = mover; //将前一个结点交付给destroy
		mover = mover->next; //指针向后移动
		free(destroy); //前一个结点被销毁
	}

	*list = NULL; //注意将头指针置零,否则头指针将成为野指针
} 
//销毁操作,将链表在内存中所占用的存储空间全部释放

void ClearBuffer(void) {
	char ch;
	while ((ch = getchar())!='\n' && ch!=EOF) {
	}
} //清除缓冲区操作

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值