介绍::本次的内容有两部分,一.我对链表的图文理解,二.具体代码的实现
一、什么是链表?
在我理解里就是非线性数据结构,与数组正相反,那么它的特点就是容易修改(即删除和插入等操作),但不容易查询,速度比数组慢很多。并且,数组的创建需要一整片链接的内存空间,而链表则不需要,是以离散的方式存储在硬盘,相对灵活很多。【链表是什么和链表特性】
链表还分为循环链表和非循环链表,我目前只会非循环。【链表有哪些类型】
链表可以用来实现栈,树,队列等数据结构,对处理实际问题有着广泛的应用【将来的应用,其实我也不是很清楚,就概略讲讲已经知道的吧】
单向非循环链表的具体结构由头节点+N数量的节点组成,而N数量的节点每个节点都由两个区域组成,一是数据域,二是指针域,数据域用于保存实际数据,指针域用于保存下一个节点的地址,两个区域组成一个节点,而头部节点较为特殊,因为它数据域不会存放数据,只负责作为一个链表的头部指向下一个节点,但我们实际遍历链表,实际是从头节点的下一个节点开始的。头节点只是为了方便以后各种对链表的操作而存在。这样,就可以完成一个由头部出发的单向非循环链表【细节】
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include <stdbool.h>
typedef struct Node
{
int data ; //这是链表的数据域
struct Node * pNext; //这是链表的指针域
}NODE,*PNODE; //这里用了typedef函数,NODE表示这个数据结构的简写,*PNODE则表示这个数据结构的内存地址
PNODE create_list(void) //创建链表的函数,分成两部分,第一是由用户输入需要的长度,第二部分是由用户手动赋值
{
int len;
int val; //将会被用来承载键盘输入的值
printf("请输入要生成的节点数");
scanf("%d",&len);
PNODE pHead = (PNODE)malloc(sizeof(NODE)); //首先生成一个头节点,方便接下来的操作
if (pHead == NULL)
{
printf("内存分配失败??");
exit(-1);
}
PNODE pTail = pHead; //由于在赋值过程中,我们需要不断创造节点,必须有一个指针始终指向尾节点才能方便我们为新的节点赋值
pTail->pNext = NULL;
for(int i=0;i<len;++i) //开始循环,次数由一开始输入的长度决定
{
printf("请输入第%d个节点的值:",i+1);
scanf("%d",&val);
PNODE pNew = (PNODE)malloc(sizeof(NODE)); //pNew就是新节点,每轮循环都会创建
if (pNew == NULL)
{
printf("内存分配失败??");
exit(-1);
}
pNew->data=val; //首先把值赋给新节点的数据域
pNew->pNext=NULL; //然后把新节点的指针域清空,因为这将会并入到链表里成为最后一个节点
pTail->pNext=pNew; //然后把上一个尾节点的指针指向新节点
pTail=pNew; //新节点成为了新的尾节点
}
return pHead; //将这个链表头指针return出去
}
void show_list(PNODE pHead) //用于遍历,观察链表
{
PNODE p; //创建一个节点用来遍历链表
for ( p = pHead->pNext;p->pNext!=NULL; p=p->pNext) //次数就是当尾节点指针域出现NULL的时候停下
{
printf("%d ",p->data);
}
printf("%d \n",p->data); //虽然此时尾节点指针域是NULL,但它仍属于链表,所以再打印一次(这里可以改进)
}
bool is_empty(PNODE pHead) //用于判断链表是否为空
{
if(NULL == pHead->pNext)//头节点下面没有任何数据则为空
{
printf("链表为空");
return true;
}
else
return false;
}
int lenght_list(PNODE pHead) //判断链表长度
{
PNODE p= pHead->pNext; //创建一个节点用来遍历链表
int len; //用来计数
for (len=0;p->pNext!=NULL;len++)
{
p=p->pNext;
}
return len;
}
void sort_list(PNODE pHead) //排序,这次用的是选择排序,在这里面就涉及了泛型的知识,不论是对数组还是链表我们都用同一种逻辑处理的,但具体实现方式却不一样
{
PNODE p,q;
int t,i,x;
int len=lenght_list(pHead);
for(i=0,p=pHead->pNext;i<len;++i,p=p->pNext)
{
for(x=i,q=p->pNext;x<len;++x,q=q->pNext)
{
if(p->data>q->data)
{
t=p->data;
p->data=q->data;
q->data=t;
}
}
}
}
bool insert_list(PNODE pHead,int pos,int val) //对链表的插入操作
{
int len = lenght_list(pHead);
PNODE p=pHead;
int i=0;
if (len<pos) //我的规则是插入的位置最少也得是尾节点后面一个,否则错误。
{
printf("节点有误,插入失败");
return false;
}
while (NULL!=p & i< pos-1) //这里我们把一个临时节点P遍历到插入位置的前一个位置,比如我要在5的位置插入,那现在P已经来到了4
{
p=p->pNext;
i++;
}
PNODE pNew=(PNODE)malloc(sizeof(NODE)); //创建一个新节点,即将要插入的节点
if (NULL==pNew)
{
printf("动态分配内存失败\n");
exit(-1);
}
pNew->data=val; //把值赋给新节点
pNew->pNext=p->pNext; //将新节点指向下一个节点
p->pNext=pNew; //将上一个节点指向新节点,这样就完成了一个节点的嵌入
}
bool delete_arr(PNODE pHead,int pos) //删除
{
int len = lenght_list(pHead); //这上半部分仍然是做一个判断,判断删除位置是否正确
PNODE p=pHead;
int i=0;
if (len<pos)
{
printf("节点有误,删除失败");
return false;
}
while (NULL!=p & i< pos-1) //这里我们依然通过临时P节点找到要删除的上一个节点,如要删5,现在P到了4
{
p=p->pNext;
i++;
}
PNODE t=p->pNext; //我们用一个临时节点T将P的指针域保存起来,否则P的下一个节点删除时候就无法,也无法释放内存了
p->pNext=p->pNext->pNext; //现在我们直接将P节点指向下下个节点,既跨过了即将被删除的节点
free(t); //释放被删除节点的内存
t=NULL;
if(len==lenght_list(pHead)-1) //最后做一轮判断,如果节点长度少了1,那么删除成功,删除失败的处理方法各位可以自己完善
{
printf("删除成功");
return true;
}
return false;
}
int main(void) //调试
{
PNODE pHead = NULL;
pHead = create_list();
show_list(pHead);
sort_list(pHead);
show_list(pHead);
insert_list(pHead,2,8);
show_list(pHead);
delete_arr(pHead,3);
show_list(pHead);
return 0;
}