在前两周我接触了一个让人头疼的东西-链表;
链表简单的说就是创建一个结构体,然后将这些每个数据存入到一个结构体里面去,通过指针的方法将这些结构体连接起来,一环扣一环,就成为了链表
官方概念
首先何谓链表?
链式存储的线性表,简称链表。链表由多个链表元素组成,这些元素称为节点。结点之间通过逻辑连接,形成链式存储结构。存储结点的内存单元,可以是连续的也可以是不连续的。逻辑连接与物理存储次序没有关系。
链表分为两个域:
值域:用于存放结点的值
链域:用于存放下一个结点的地址或位置
简单的介绍:
1- 值域结构体里面包含了咱们要放进去的数据,(这里的演示我简单的放一个存储学生姓名的链表)
2- 链区域 结构体里面还包含一个指向下一个数据的指针。这个指针的将指向下一个元素的地址(这里叫节点)
下来咱们先创建一个结构体:
typedef struct xx
{
char name[15];//
struct xx *next;
}Node;
next 是指向下一个节点的指针 name就是我们存放名字的地方 每一个结构体都有这两部分组成
typedef:是一个高级数据特性,利用它可以为某一类型自定义名称
eg:
typedef int lengh;
//关键就在于最后一个单词 这里以后lengh就是int类型的代表
eg:
lengh a=int a;
所以我们上面用Node 以后来代替struct xx类型,算是一种方便吧
好接下开我们来看看放入数据的方法- 头插法
#include<stdio.h>
#include<stdlib.h>
void creatlink(Node *head,int n)//头插法
{
int i;
Node *p;
for(i=0; i<n; i++)
{
p=(Node*)malloc(sizeof(Node));//这里存在一个强制类型的转换,为我们的输入的数据开辟一个空间,malloc函数在stdlib文件里;
gets(p->name);
p->next = head->next;//p所指新结点的指针域next指向head中的开始结点
head->next=p;//头结点的指针域next指向p结点,使得p成为新的开始结点
}
}
这里有一个head 表头指针,里面部分放任何东西,其实链表的创建可以不要这个表头指针,有表头指针的优势在于以后遍历时可以看到遍历到第几个元素;
注意:
头插法你输入 1 2 3 4 5 6
其实数据是以6 5 4 3 2 1的方式进行存储的,好比我们把没输入进去新的数字插入到值之前数据的前面的意思;
尾插法:
和头插法的不一样就是把输入的新的数据插入到以前数据的后面
:输入 12 3 4 5 6
就是以1 2 3 4 5 6的方式存储的
void createHeadList()
{
int n;
Node * head, *tail, *p;//tail是一个尾指针
//创建头结点
head = (Node *)malloc(sizeof(node));
head->next = NULL;//
while(scanf("%d",&n) == 1)
{
p = (Node *)malloc(sizeof(node));
p->data = n;
p->next =NULL;//p默认是我们的最后一个数据 所以他的尾部为空
tail->next = p;这里注意区分和头插法的区别,tail是我们设置的尾指针 用来一直指向链表的末尾
tail = p;
}
return head;
}
接下来的操作是建立在熟悉创建链表的基础上
void printlink(Node *head)//打印链表
{
Node *p;
p=head->next;
while(p!=NULL)//是否到结尾
{
puts(p->name);
p=p->next;
}
}
int lenlink(Node *head)//求链表的长度
{
int len;
Node *p;
for(len=0,p=head->next;p!=NULL;len++)//先给p赋值首个元素;
p=p->next;//p指向下一个元素;
return len;
}
//插入节点
void sertlink(Node *head,Node *p, int i)//q指向第i个节点,指针p指向需要插入的节点,p带来的是新的节点注意以下的注释
{
Node *q;
int n=0;
for(q=head; n<i&&q->next!=NULL; n++)
{
q = q->next;
p->next = q->next;// 链接后面的指针,将插入的节点和之前的进行🔗,q本来的next赋值给新节点p的next,
q->next = p;//链接前面的指针,新的插入的节点p的手地址是由原节点q的末尾指向,所以完成了所谓的链接;~!
}
}
//删除节点
void deletelink(Node *head,int i)//这里不用穿入新的节点,和插入一样,q指向删除节点的前一个i-1节点,p指向删除的节点
{
Node *p,*q;
int n;
for(n=0,q=head; n<i-1&&q->next!=NULL;n++)//
{
q = q->next;//寻找第i个节点,这里寻找到第i个节点是通过i-1来找到的
if(i>0 && q->next!=NULL)
{
p = q->next;//q是第i-1个节点,所以q的next赋值给p,p就成为了我们要删除的那个节点
q ->next = p->next;//摘除节点,节点交接
free(p);//删除以后释放原来的内存,致辞我们的单链表的基本操作就完成了🤪
}
}
}
大家一定一定要熟悉上面的单链表的基本操作之后再来看下面的
链表的排序-1
插入排序;
不知插入排序的小伙伴可以去我的上一篇博客看看哦🐶
对于一串数据的插入排序大家都会,可是放到了操作难度极高的链表上就让人有些头大了!
不仅仅得时刻注意链表的头指针,还有将数据交换,所以大家得学会一些基本操作,多看几遍就懂了!!!
⚠️注意:
void sort(Node * &L){
Node *p,*pre,*q;
p = head->next->next; // 先保存下链表的第二个元素,因为下一步要将链表变成只有一个元素的有序表。
head->next->next = NULL; // 将断点之前的表变成只有一个元素的有序表
// 从新链表的第二个元素开始遍历整个新链表直至表尾
while(p != NULL){
q = p->next;
pre = head; // 先用pre来保存新表,(利用头指针head。
while(pre->next !=NULL && pre->next->data < p->data) // 遍历pre所指向的有序表head,直至找到比p大的节点
pre = pre->next;
p->next = pre->next;
pre->next = p;
p = q;
}
}
这里我只放出来了函数代码;
在说一点就是插入排序的时间复杂度较高,但空间复杂度很低o(1);
这次的代码还是在网上参考了一些不是很成熟
有什么建议和想法大家可以评论区讨论;