带头结点单链表(详解)

 更新时间:2023年07月02日 11:52:01   作者:CD4356  
这篇文章主要介绍了带头结点单链表 (详解),需要的朋友可以参考下

cd4356

单链表结构体

  • 结构体后的*List是一个指向结构体的指针类型,我们通过它来定义该类型的指针。
  • 如:List  p ;  则这个p就是指向LinkedList结构体的一个指针,也就是单链表的头指针。(所以说头指针是必然存在的,但单链表不一定有头结点,注意区分头指针和头结点)
typedef struct LinkedList {
	int data;                  //数据域
	struct LinkedList *next;   //指针域,指向后继结点,存放后继结点的地址
}*List; //List <==> List * ,这里的List是单链表的头指针

带头结点 和 不带头节点 的初始化 带头结点单链表的初始化。①头结点初始化时,其next域必须置为空 head->next = NULL;。②头结点的data数据域如果要用来记录链表长度,则需初始化为0 head->data = 0;

cd4356

//申请一个头结点 或 初始化一张空表
List create_head_node() {
	List head = (List)malloc(sizeof(LinkedList)); //创建头结点,并让头指针指向头结点 (带头结点单链表)
	if (head == 0) { //结点空间申请失败,该判断语句可有可无
		return NULL;
	}
	head->next = NULL; //head是头结点,h->next是头结点的地址,该地址是第一个结点的地址,地址为NULL,即为空表
	//head->data不用操作,但若想用头结点数据域保存链表长度,可以设置为head->data = 0;
	head->data = 0;
	return head;
}

不带头结点单链表的初始化

cd4356

List link_list_create(){
	List p;
	p = NULL; 
	return p;
}

求链表长度

单链表求长度有两种方式

① 用头结点的data数据域记录链表长度(只适用于带头结点的链表)。创建头结点时,头结点的data初始化为0。成功执行插入操作后,头结点数据域+1。成功执行删除操作后,头结点数据域-1。时间复杂度为O(1),(故链表带头结点,则推荐用这种方式)

head->data = 0; //创建头结点时,将头结点数据域初始化为0
head->data++; //每插入一个元素,头结点数据域+1
head->data--; //每删除一个元素,头结点数据域-1
head->data; //获取链表长度

② 遍历链表获取长度,时间复杂度为O(n)

//求长度
int link_list_length(List head) {
	List p = head->next;
	int len = 0;
	while (p) {
		len++;
		p = p->next;
	}
	return len;
}

空表判断 带头结点的空表判断head是头结点,h->next是头结点的地址,该地址是第一个结点的地址,地址为NULL,即为空表

boolean isEmpty(List p){
	if(p->next == NULL){
		return TRUE;
	}
	return FALSE;
}
或者 (使用头结点数据域记录链表长度时,可用该方法)
boolean isEmpty(List p){
	if(p->data == 0){
		return TRUE;
	}
	return FALSE;
}

不带头结点的空表判断 p == NULL;   p表示的是第一个结点的地址,p = NULL 表示第一个结点的地址为空,也就是空表

boolean isEmpty(List p){
	if(p == NULL){
		return TRUE;
	}
	return FALSE;
}

头插法

在链表头部插入结点(即作为链表第一个元素),时间复杂度为O(1)

cd4356cd4356

//头插
boolean head_insert(List head, int x) {
	List p = (List)malloc(sizeof(LinkedList)); //申请一个结点,并将要插入的元素填入该结点的数据域
	p->data = x;
	p->next = head->next;
	head->next = p;
	head->data++; //结点插入成功,链表长度+1
	return TRUE;
}

尾插法

在链表尾部插入结点(即作为链表最后一个元素),时间复杂度为O(n)。

新结点插入链表尾部时,新结点的next域必须置为空,即:newNode->next = NULL; 。因为单链表尾结点的next域必须为null,否则我们在调用尾结点的next指针p->next时,系统判断尾结点的next没有值,就会动态地给它分配一个未知地址(而不是帮你将next域置为空)。正常情况下,虽然并不会出现问题,但在遍历链表 或 按内容获取结点 或 第二次插入为节点时,程序就会陷入死循环,最终耗尽cpu性能。你可以将tail_insert()方法中的newNode->next = NULL;这行代码注释掉,然后调用我们后面讲到的show()方法进行测试,你就会看到show()方法会不停息的打印输出一个个未知地址,直至内存耗尽,系统奔溃

cd4356

//尾插
boolean tail_insert(List head, int x) {
	List newNode = (List)malloc(sizeof(LinkedList)); //申请一个结点,并将要插入的元素填入该结点的数据域
	newNode->data = x;
	//newNode->next是动态分配的,如果你不置为空,系统就会动态地给它分配一个未知地址。
	newNode->next = NULL;
	List p = head; //用p结点做记录
	while (p->next != NULL) { //遍历链表,直至尾结点
		p = p->next;
	}
	p->next = newNode;  //让尾结点指针,指向新结点
	head->data++;  //新结点插入成功,链表长度+1
	return TRUE;
}

指定位置插入 在第k个位置插入新结点。需要先遍历链表,找到第k-1个结点,然后再修改新结点指针和第k-1个结点的指针,即可实现在第k个位置插入新结点操作。

cd4356

//指定位置插入
boolean insert(List head, int k, int x) {
	if (k < 1) {
		printf("插入位置非法!");
		return FALSE;
	}
	List p = head;
	int i = 0; //用i来记录结点位置
	while (p->next != NULL && i < k - 1) {
		i++;
		p = p->next;
	}
	if (i + 1 == k) {
		List tmp = (List)malloc(sizeof(LinkedList)); //申请一个结点,并将要插入的元素填入该结点的数据域
		tmp->data = x;
		tmp->next = p->next;
		p->next = tmp;
		head->data++;  //新结点插入成功,链表长度+1
		return TRUE;
	} else {
		printf("插入位置非法!");
		return FALSE;
	}
}

按位序查找

//按位序查找
List get(List head, int k) {
	if (k < 1) {
		printf("查找位置非法!");
		return NULL;
	}
	List p = head;
	int i = 0;
	while (p->next != NULL && i < k) { //找到第k个结点
		i++;
		p = p->next;
	}
	if (i == k) {   //判断i是否等于要查找结点的位序
		return p;
	}else{
		printf("查找位置非法!");
		return NULL;
	}
}

删除第1个结点

//删除第1个结点
boolean del_first(List head) {
	if (head->next != NULL) {
		head->next = head->next->next;  //让头结点next指针,指向头结点下一个结点的next指针所致地址
		head->data--;  //链表长度减1
		return TRUE;
	}
	return FALSE;
}

删除第k个结点

//删除第k个结点
boolean del(List head, int k) {
	List p = head;
	if (p->next == NULL && k < 1) {
		printf("删除位置非法!\n");
		return FALSE;
	}
	int i = 0; //用i记录结点位置
	while (p->next != NULL && i < k - 1) { //找到待删结点的前一个结点
		i++;
		p = p->next;
	}
	if (i + 1 == k) {  //判断i是不是k结点的前一个结点的位置
		p->next = p->next->next;
		head->data--;  //链表长度减1
		return TRUE;
	} else {
		printf("删除位置非法!\n");
		return FALSE;
	}
}

遍历链表,显示数据 前面讲到了,如果在新结点插入链表尾部时,如果新结点next指针没有置为NULL,则在show()遍历链表时,将链表的结点数据输出后,方法不会中断,而是继续输出一个个未知地址,直至内存空间耗尽。

//显示数据
void show(List head) {
	List p = head->next;
	while (p != NULL) {
		printf("%d ", p->data);
		p = p->next;
	}
}

全部代码

#include <stdio.h>
#include <stdlib.h> //malloc需要此头文件
typedef enum {FALSE, TRUE} boolean;
//结构体
typedef struct LinkedList {
	int data;
	struct LinkedList *next;
} *List; //List <==> List *
//申请一个头结点,并初始化
List create_head_node() {
	List head = (List)malloc(sizeof(LinkedList)); //创建头结点,并让头指针指向头结点 (带头结点单链表)
	if (head == 0) { //结点空间申请失败,该判断语句可有可无
		return NULL;
	}
	head->next = NULL; //head表示的是头结点的地址,h->next就是头结点的下一个结点,即第一个结点的地址为空,也就是空表
	//head->data不用操作,但若想用头结点数据域保存链表长度,可以设置为head->data = 0;
	head->data = 0;
	return head;
}
//头插
boolean head_insert(List head, int x) {
	List p = (List)malloc(sizeof(LinkedList)); //申请一个结点,并将要插入的元素填入该结点的数据域
	p->data = x;
	p->next = head->next;
	head->next = p;
	head->data++; //结点插入成功,链表长度+1
	return TRUE;
}
//尾插
boolean tail_insert(List head, int x) {
	List newNode = (List)malloc(sizeof(LinkedList)); //申请一个结点,并将要插入的元素填入该结点的数据域
	newNode->data = x;
	//newNode->next是动态分配的,如果不置为空,系统就会默认分配一个未知地址
	newNode->next = NULL;
	List p = head; //用p结点做记录
	while (p->next != NULL) { //遍历链表,直至尾结点
		p = p->next;
	}
	p->next = newNode;  //让尾结点指针,指向新结点
	head->data++;  //新结点插入成功,链表长度+1
	return TRUE;
}
//指定位置插入
boolean insert(List head, int k, int x) {
	if (k < 1) {
		printf("插入位置非法!");
		return FALSE;
	}
	List p = head;
	int i = 0; //用i来记录结点位置
	while (p->next != NULL && i < k - 1) {
		i++;
		p = p->next;
	}
	if (i + 1 == k) {
		List tmp = (List)malloc(sizeof(LinkedList)); //申请一个结点,并将要插入的元素填入该结点的数据域
		tmp->data = x;
		tmp->next = p->next;
		p->next = tmp;
		head->data++;  //新结点插入成功,链表长度+1
		return TRUE;
	} else {
		printf("插入位置非法!");
		return FALSE;
	}
}
//按序号查找
List get(List head, int k) {
	if (k < 1) {
		printf("查找位置非法!");
		return NULL;
	}
	List p = head;
	int i = 0;
	while (p->next != NULL && i < k) { //找到第k个结点
		i++;
		p = p->next;
	}
	if (i == k) {
		return p;
	} else {
		printf("查找位置非法!");
		return NULL;
	}
}
//删除第1个结点
boolean del_first(List head) {
	if (head->next != NULL) {
		head->next = head->next->next;
		head->data--;
		return TRUE;
	}
	return FALSE;
}
//删除第k个结点
boolean del(List head, int k) {
	List p = head;
	if (p->next == NULL && k < 1) {
		printf("删除位置非法!\n");
		return FALSE;
	}
	int i = 0; //用i记录结点位置
	while (p->next != NULL && i < k - 1) { //找到待删结点的前一个结点
		i++;
		p = p->next;
	}
	if (i + 1 == k) {  //判断i是不是k结点的前一个结点的位置
		p->next = p->next->next;
		head->data--;
		return TRUE;
	} else {
		printf("删除位置非法!\n");
		return FALSE;
	}
}
//显示数据
void show(List head) {
	List p = head->next;
	while (p != NULL) {
		printf("%d ", p->data);
		p = p->next;
	}
}
int main() {
	List head = create_head_node();
	printf("当前单链表长度为:%d\n\n", head->data);
	head_insert(head, 15);
	head_insert(head, 25);
	head_insert(head, 35);
	printf("第1个结点元素为:%d\n", get(head, 1)->data);
	printf("第2个结点元素为:%d\n", get(head, 2)->data);
	printf("第3个结点元素为:%d\n", get(head, 3)->data);
	printf("当前单链表长度为:%d\n\n", head->data);
	tail_insert(head, 45);
	tail_insert(head, 55);
	printf("第1个结点元素为:%d\n", get(head, 1)->data);
	printf("第2个结点元素为:%d\n", get(head, 2)->data);
	printf("第3个结点元素为:%d\n", get(head, 3)->data);
	printf("第4个结点元素为:%d\n", get(head, 4)->data);
	printf("第5个结点元素为:%d\n", get(head, 5)->data);
	printf("当前单链表长度为:%d\n\n", head->data);
	insert(head, 6, 65);
	insert(head, 2, 75);
	printf("第1个结点元素为:%d\n", get(head, 1)->data);
	printf("第2个结点元素为:%d\n", get(head, 2)->data);
	printf("第3个结点元素为:%d\n", get(head, 3)->data);
	printf("第4个结点元素为:%d\n", get(head, 4)->data);
	printf("第5个结点元素为:%d\n", get(head, 5)->data);
	printf("第4个结点元素为:%d\n", get(head, 6)->data);
	printf("第5个结点元素为:%d\n", get(head, 7)->data);
	printf("当前单链表长度为:%d\n\n", head->data);
	show(head);
	printf("\n\n");
	del_first(head);
	show(head);
	printf("\n\n");
	del(head, 4);
	show(head);
	return 0;
}

到此这篇关于带头结点单链表 (详解)的文章就介绍到这了,更多相关带头结点单链表内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C++ CPU的局部性原理两种类型解析

    C++ CPU的局部性原理两种类型解析

    这篇文章给大家介绍了CPU的局部性原理,包括时间局部性、空间局部性以及如何通过缓存利用这些局部性来提高程序性能,局部性原理是现代计算机体系结构和高性能编程的核心思想,感兴趣的朋友跟随小编一起看看吧
    2026-02-02
  • C++中的priority_queue容器使用及说明

    C++中的priority_queue容器使用及说明

    文章主要介绍了C++标准模板库中的优先级队列(priority_queue)的功能、实现原理、使用方法及应用场景,优先级队列提供按优先级排序的数据存储方式,默认为最大堆实现,通过分析其构造函数、操作函数、性能考虑等内容,展示了优先级队列在实际应用中的优势与注意事项
    2026-04-04
  • 详解桶排序算法的思路及C++编程中的代码实现

    详解桶排序算法的思路及C++编程中的代码实现

    桶排序即是先把每个桶中的元素进行排序然后遍历桶依次列出元素的算法,桶排序在元素较少的情况下很高效,以下我们就来详解桶排序算法的思路及C++编程中的代码实现:
    2016-07-07
  • C++11可变参数模板的具体实现

    C++11可变参数模板的具体实现

    C++11引入的可变参数模板是一项非常强大的特性,它极大地提升了模板的扩展性,可变参数模板允许我们定义可以接受任意数量和类型参数的模板,这在处理不定数量参数的场景中非常有用,感兴趣的可以了解一下
    2025-06-06
  • C语言实现带头双向循环链表

    C语言实现带头双向循环链表

    本文主要介绍了C语言实现带头双向循环链表,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03
  • C++命名空间5种常见用法实例解析

    C++命名空间5种常见用法实例解析

    这篇文章主要介绍了C++命名空间5种常见用法实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-06-06
  • C/C++开发中extern的一些使用注意事项

    C/C++开发中extern的一些使用注意事项

    这篇文章主要为大家介绍了C/C++开发中extern一些使用注意事项的事例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-01-01
  • 详解C++11 线程休眠函数

    详解C++11 线程休眠函数

    这篇文章主要介绍了C++11 线程休眠函数的相关资料,帮助大家更好的理解和学习C++11,感兴趣的朋友可以了解下
    2020-10-10
  • select函数实现高性能IO多路访问的关键示例深入解析

    select函数实现高性能IO多路访问的关键示例深入解析

    这篇文章主要为大家介绍了select函数实现高性能IO多路访问的关键示例深入解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09
  • C语言数据结构之堆排序的优化算法

    C语言数据结构之堆排序的优化算法

    堆排序Heap Sort就是利用堆进行排序的方法,下面这篇文章主要给大家介绍了关于C语言数据结构之堆排序的优化算法的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-04-04

最新评论