C语言之单链表及各项操作介绍

3 篇文章 0 订阅
3 篇文章 0 订阅

近况

因为疫情被困在了英国,国内的招聘都错过了,原本预约好的面试也都不能如期参加。感觉出国深造的这几年都浪费了。不过也不能灰心。在这个每天都只能待在家的期间,还是要好好学学对以后职场有帮助的技能吧。
要学的知识真的很多,因为我是通信工程专业毕业的,所以就选择了钻研一下数据结构和算法。
其中我在之前几个月也写过一篇过于这领域的文章,就是字符串搜索以及替换。不过现在看来,还是非常皮毛的,不过对这话题感兴趣的小伙伴也可以看看:字符串搜索及替换
在之后几篇文章中,我会依次的介绍:

  1. 单链表及各项操作介绍,即本文
  2. 单链表初始化
  3. 单链表基本操作功能实现,如:打印(遍历),查询,定位,插入,删除,链表长度
  4. 单链表反转,排序

单链表介绍

什么是单链表

有C语言基础的同学一定认识数组或者字符串。以数组为例,数组最大尺寸都是一定的,而且各个相邻的数据元素之间它们的存储空间也是相邻的。所以数组内的元素查找特别方便特别的快。不过他也有缺点。一个就是刚才提到的最大尺寸是一定的。其实很多时候,我们对于数据元素的数量是不确定,而且时常会有变动。假如我们现在要做数据采样,把数据数值都按顺序记录在数组内,那么这个时候,数组的尺寸我们除了先预留一个很大的空间就别无它法了,如果收集到的数据少很很占存储空间,而且也有可能预留空间不够大。第二个缺点就是,假如我们想删除第i个位置里的数据元素,那么i以后的所有元素,都要相应的往前位移一位,假如有1000个数据在i后面,那么位移这个动作就要做1000遍,(很耗时间)。
所以这个时候就有了链表。链表分很多种,例如单链表,双链表以及循环链表。链表的概念如图下图显示:
在这里插入图片描述
可以看到单链表的基本单位是node(节点)。而这些节点可以通过结构函数struct()来定义。只需要在结构函数里定义一个想要记录的data type(数据类型)以及指针就好了。对于指针指向的类型是什么呢?从图中可以看出来,指针指向的是下一个节点,所以他的类型就是节点的本身,就是这个数据结构的本身。具体可以参考代码详解。
除了节点外,还有两个是比较特殊的,分别是头和尾。头一般是不记录任何的数据类型信息,它主要的功能式告诉我们这个链表第一个数据元素的存储位置在哪里。看到这里就会发现,这存储位置不是连续的,不是相邻的,因为它需要通过前一个节点告诉后一个节点的位置。这是相对应数组静态存储方式,叫做动态存储方式。至于尾,NULL,其实就是说如果这个节点它没有指向任何节点时,那么这个节点就是最后一个数据元素了。NULL就是逻辑地址里的0*0000,一般是访问不到的,所以这里面是不会存放数据的。
相对于数组,链表元素的增加比较方便,只需要初始化一个节点,然后让尾节点指向它就可以了。而且如果要在中途添加或删减数据,只需要改动前一个节点指向的位置即可。不过在这里也要提一下,链表也有其本身的缺点,就是访问/查询不方便。试想一下,倘若想知道第i个节点里的数值,因为它的位置是独立的,而我们只知道头结点的位置,所以不得不从头开始去寻找它所在的位置来读取数据。
至于双链表,其实就是在每个节点处多一个指向前节点的指针,这样的话,我们不仅可以往后读取数据,而且能更方便的往前读取数据。不过根据网上的资料说,目前单链表的应用比双链表的应用更为广泛。其实也不难理解,很多时候我们不仅仅要权衡不同情况运行时间的快慢,还有的就是硬盘材料的成本。倘若大量使用双链表,因为双链表多了一个指向前节点的指针,相应这个节点所占的内存空间也就变大了,当长度为n时,可以看作双链表所消耗的内存空间是单链表的两倍。
而循环链表就是,链表的尾部永远指向链表头,形成一个环状,对于单链表来说,优势也没有太大的明显。

代码内容

在这次的码字当中,我代码主要实现了多个功能,这些都对于学习单链表的朋友来说的必修课吧。其中它们包括了:

  1. 定义一个数据结构。
  2. 链表头(header)的创建。
  3. 链表数据初始化。(其实就是把数值依次放到链表里)
  4. 在while loop里面的功能选择:(其中这一块使用到了函数指针的应用)
    a. 链表打印。
    b. 数据查询。例子:假如想查询第3位数值,输入3,响应便是第三位数据的数值。
    c. 定位操作。例子:假如想查这个链表单中,4这个数值会是在哪一位,若没有,就会没有响应,若有多位,响应都会把位置都输出一遍。
    d. 插入新元素。例子:通过输入想要插入的位置和数值来增加单链表的元素。
    e. 删除元素。 例子:输入想要删除的某一位,单链表对应位置的元素(结构体)就会被删除同时free掉。
    f. 链表长度。由于每次d和e的操作都会使得链表长度发生变化,在这一步单中能让用户了解自己单链表的实时长度。
    g. 单链表反转。这是一个比较经典的算法,在之前某些公司的网试也遇到这个问题。其中它区分非回归和回归算法。代码中,两个算法都实现了。
    h. 排序。排序方法有很多种,其中在这里用到的代码是优化泡沫排序。泡沫排序是最基本的排序算法了。建议有时间同学一定要学习其他的排序算法,例如:快速排序和希尔排序。
    i. 退出。
  5. 跳出while loop后,代码不能直接结束,还有一件最最最重要的步骤就是内存释放。因为单链表各个元素都是通过动态分配内存的,所以把他们从内存里清出来,以后就可以用了。

以下都是详细代码,代码全凭个人编写,建议要用以下代码的话在visual studio 2019实现 ,希望给各位带来帮助。至于各个步骤的详细解读,小冯(我)会尽快给各位编辑出来的。

代码

source.c

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

int main()
{
	int num, i = 0;
	//---------------------------------------------------------------
	//创建头标
	printf("正在创建头链表......\n\n");
	node* head = _head();
	printf("头链表创建成功。\n\n");
	//---------------------------------------------------------------
	//输入初始化数据个数
	printf("多少个数字你想输入?(输入数字)\n");
	scanf_s("%d", &num);
	head->value = num;
	//---------------------------------------------------------------
	//插入数据
	printf("\n开始输入你想输入的数字。\n");
	_insert(head, num);
	printf("\n完成输入,链表初始化完成。\n\n");
	//---------------------------------------------------------------
	//功能
	while (1) {
		p_fun();
		printf("请选择以上一功能:\n");
		scanf_s("%d", &i);
		char c = getchar();
		system("cls");
		if (i >= 0 && i <= sizeof(fa) / sizeof(0)) {
			if (i != sizeof(fa) / sizeof(0)) {
				(*fa[i])(&head);
			}
			else {
				break;
			}
		}
		else {
			printf("请输入一个正确的数字\n");
		}
		printf("\n\n");
	}
	_free(&head);
	system("pause");
	return 0;
}

header.h

#pragma once
#include "preheader.h"

/*=============================================================*/
//打印功能表
void p_fun() {
	printf("\
	0. 打印\n\
	1. 数据查询\n\
	2. 定位操作\n\
	3. 插入新元素\n\
	4. 删除元素\n\
	5. 链表长度\n\
	6. 单链表反转\n\
	7. 排序\n\
	8. 退出\n\n");
}
/*=============================================================*/
//打印
void f0(node** head) {
	printf("列表打印结果:\n");
	_print(*head);
}
/*=============================================================*/
//数据查询
void f1(node** head) {
	printf("请问你想查询第几位数据: ");
	int check;
	scanf_s("%d", &check);
	node* pos = _check(*head, check);
	if (pos)
		printf("第 %d 位数据为 %d\n", check, pos->value);
}
/*=============================================================*/
//定位操作
void f2(node** head) {
	printf("请问你想查找的数据是: ");
	int target;
	int count = 0;
	scanf_s("%d", &target);
	printf("你想查找的数据它的位置在: ");
	if (empty(*head))		_locate(*head, target, count);
	printf("\n");
}
/*=============================================================*/
//插入新元素
void f3(node** head) {
	printf("在第几位插入数据: ");
	int n_insert;
	scanf_s("%d", &n_insert);
	printf("插入的数值为: ");
	int n_value;
	scanf_s("%d", &n_value);
	_newInsert(*head, n_insert, n_value);
	_print(*head);
	(*head)->value = (*head)->value + 1;
}
/*=============================================================*/
//删除元素
void f4(node** head) {
	printf("删除第几位数据: ");
	int del;
	scanf_s("%d", &del);
	_delete(*head, del);
	_print(*head);
	(*head)->value = (*head)->value - 1;
}
/*=============================================================*/
//链表长度
void f5(node** head) {
	printf("这个链表总共有 %d 个数据。\n", (*head)->value);
}
/*=============================================================*/
//单链表递归反转
void f6(node** head) {
	printf("单链表反转结果为:\n");
	node* p = _reverse((*head)->next);
	(*head)->next = p;
	_print(*head);

}
/*=============================================================*/
//排序
void f7(node** head) {
	printf("链表排序后结果为:\n");
	_queue(*head);
	_print(*head);
}

/*=============================================================*/
//释放
void _free(node** head) {
	node* p;
	while (*head != NULL) {
		p = *head;
		*head = p->next;
		free(p);
	}
}

void (*fa[])(node**) = { f0,f1,f2,f3,f4,f5,f6,f7 };

preheader.h

#pragma once
typedef struct node {
	int value;
	struct node* next;
}node;

//----------------------------------------------------------
//创建头标													
node* _head() {
	node* head = (node*)malloc(sizeof(node));
	if (head == NULL)
		exit(-1);//head=_head();							
	head->next = NULL;
	return head;
}
//----------------------------------------------------------
//插入新数据												
void _insert(node* head, int num) {
	int v, i = 0;
	while (i < num) {
		node* current = head;
		node* new = (node*)malloc(sizeof(node));
		printf("第 %d 位数据数值: ", i + 1);
		scanf_s("%d", &v);
		char c = getchar();
		new->value = v;
		new->next = NULL;
		while (current->next != NULL) {
			current = current->next;
		}
		current->next = new;
		i++;
	}
}
//----------------------------------------------------------
//空链确认													
int empty(node* head) {
	if (head->next == NULL) {
		printf("这是一个空链表。\n");
		return 0;
	}
	return 1;
}
//----------------------------------------------------------
//通过遍历打印输出各个元素值								
void _print(node* head) {
	if (empty(head)) {
		node* print = head->next;
		while (print != NULL) {
			printf("%d,", print->value);
			print = print->next;
		}
	}
}
//----------------------------------------------------------
//数据查询													
node* _check(node* head, int check) {
	node* h = head;
	int j = 0;
	//检验是否为空链										
	if (!empty(head)) {
		return NULL;
	}
	//检验输入查询是否正常									
	else if (check < 1) {
		printf("请输入一个正确的方位。\n");
		return NULL;
	}
	//开始遍历查询											
	else {
		while (h->next != NULL && j < check) {
			h = h->next;
			j++;
		}
		//如果j小于check,说明了check太大,正常期望j等于check,不存在j大于的情况
		if (j < check) {
			printf("没有你想查询的位数。链表没有这么长。\n");
			printf("这个链表的尺寸为: %d 位.\n", j);
			return NULL;
		}
		//正常输出											
		else {
			return h;
		}
	}
}
//----------------------------------------------------------
//数据定位													
void _locate(node* head, int target, int i) {
	node* local = head;
	local = local->next;
	i++;
	while (local != NULL) {
		if (local->value == target)	printf("%d ", i);
		local = local->next;
		i++;
	}
}
//----------------------------------------------------------
//插入新元素												
int _newInsert(node* head, int n_insert, int n_value) {
	node* pre;
	int j = 0;
	pre = head;
	while (pre->next != NULL && j < n_insert - 1) {
		pre = pre->next;
		j++;
	}
	if (j != n_insert - 1) {
		printf("抱歉,我们没有查找到你想插入的方位。\n");
		return 0;
	}
	node* p = _head();
	p->next = pre->next;
	p->value = n_value;
	pre->next = p;
	return 0;
}
//----------------------------------------------------------
//删除元素
int _delete(node* head, int del) {
	node* p = head;
	if (!empty(head))	return 0;
	int i = 1;
	node* q = p;
	p = p->next;
	while (p->next != NULL && i < del) {
		q = p;
		p = p->next;
		i++;
	}
	if (i != del) {
		printf("抱歉,我们没有查找到你想插入的方位。\n");
		return 0;
	}
	q->next = p->next;
	free(p);
	return 1;
}
//----------------------------------------------------------
//单链表反转
node* _reverse(node* head) {
	if (head == NULL || head->next == NULL) return head;
	node* p = _reverse(head->next);
	head->next->next = head;
	head->next = NULL;
	return p;
}
//----------------------------------------------------------
//排序
void _queue(node* head) {
	for (node* p = head->next;p->next != NULL;p = p->next) {
		int flag = 0;
		for (node* q = head->next;q->next != NULL;q = q->next) {
			if (q->value > q->next->value) {
				int t = q->value;
				q->value = q->next->value;
				q->next->value = t;
				flag++;
			}
		}
		if (flag == 0)	break;
	}
}
  • 9
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值