/*
日期:2021.11.27
作者:木子阳
编译工具:Visual C
线性表的链式存储–链表
采用链式存储结构的线性表称为链表。
链式存储结构:它是把数据元素和指针定义成一个存储体,使用指针将发生练习的数据元素链接起来的一种计算机存储数据方法。
特点:任意两个在逻辑上相邻的数据元素在物理上不一定相邻,数据元素的逻辑次序是通过链中的指针实现的。
1单链表
单链表包括两个域:数据域用来存储结点的值,指针域用来存储数据元素的直接后驱的地址(或地址)。链表正式通过每个结点的指针域将线性表的n个结点按其逻辑顺序链接在一起。鱿鱼链表的每一个结点只有一个指针域,故将这种链表又称为单链表。
由于单链表中每个结点的存储地址是存放在其前驱结点的指针域中的,而第一个结点无前驱,因而应设一个头指针H指向第一个结点。同时,由于表中最后一个节点没有直接后继,则指定线性表最后一个结点的指针域为‘空’(NULL),这样对于整个链表的存储必须从头指针开始。有时为了操作的方便,还可以在单链表的第一个结点之前附设一个头结点,此时头指针不再指向表中的第一个结点,而是指向头结点。
头指针:是指向链表中第一个结点(或为头结点,或为首元结点)的指针。
头结点:是在链表的首元结点之前附设的一个结点;数据域只放空表标志和表长等信息,它本身不计入长度。
首元结点:是指链表中存储线性表第一个数据元素的结点(如a 1)。
1.1初始化单链表
/*1、初始化单链表*/
InitList(LinkList H)
{
H=(LinkList)malloc(sizeof(Node));//建立一个头结点
H->next=NULL;//建立空的单链表H
}
1.2建立单链表
a. 头插法
从一个空表开始,每次读入数据,都要申请一个新结点,将读入数据存放到结点的数据域中,然后将新结点插入到表头结点之后,直到读入结束标志(在本文中结束标志设为‘\n’即回车)为止。
算法描述如下:
void CreateList(LinkList H)
{
Node *s;//
char c;//输入元素变量
int flag=1;//设置一个不等于0的初值,使得循环开始
while(flag)
{
c=getchar();//输入元素,开始建表
if(c!='\n')//设置输入回车循环结束
{
s=(Node*)malloc(sizeof(Node));//建立新结点
s->data=c;
s->next=H->next;
H->next=s;
}
else flag=0;
}
}
头插法得到的单链表的逻辑顺序与输入元素的顺序是相反的,所以又称头插法建表法为逆序建表法。
b. 尾插法
该方法是将新结点插入到当前链表的表尾,为此需要增加一个尾指针(在本文中是r),使之指向当前 单链表的表尾。
算法描述如下:
void GreateFromTail(LinkList H)
{
Node *r,*s;
int flag=1;
char c;
r=H;
printf("请开始输入元素(输入'\\n'结束):\n**格式如:123ad**\n");
while(flag)
{
c=getchar();
if(c!='\n')
{
s=(Node*)malloc(sizeof(Node));
s->data=c;
r->next=s;
r=s;
}
else
{
flag=0;
r->next=NULL;
}
}
}
1.3删除操作
算法描述如下:
int DelList(LinkList L,int i)//i为被删除元素位置
{
Node*p,*r;
int k;
p=L;
k=0;
while((p->next!=NULL)&&(k<i-1))
{
p=p->next;
k=k+1;
}
if(k!=i-1)
{
printf("删除的位置不合理!\n");
return(ERROR);
}
r=p->next;
p->next=p->next->next;
//free(r);//释放被删除的结点所占的空间
return(OK);
}
1.4插入操作
算法描述如下:
int InsList(LinkList L,int i,char e)//i为插入位置,e为插入元素
{
Node *pre,*s;
int k;
pre=L;
k=0;
while((pre->next!=NULL)&&(k<i-1))
{
pre=pre->next;
k=k+1;
}
if(k!=i-1)
{
printf("插入位置不合理!\n");
return(-1);
}
s=(Node*)malloc(sizeof(Node));
s->data=e;
s->next=pre->next;
pre->next=s;
return(0);
}
综合运用
1、
#include <stdio.h>
#include <stdlib.h>//动态存储分配函数
#define OK 1
#define ERROR 0
typedef struct Node//结点类型定义
{
int data;
struct Node* next;
}Node,*LinkList;//*LinkList为结构指针类型
//Node*与LinkList同为结构指针类型,用LinkList说明变量时,强调变量是某个单链表的头指针,从而提高程序的可读性
/*0、输出链表中的元素*/
void Display(LinkList H)
{
Node *p = H->next;
printf("链表元素如下:\n");
while(p)
{
printf("%c ", p->data);
p=p->next;
}
printf("\n");
}
/*1、初始化单链表*/
InitList(LinkList H)
{
H=(LinkList)malloc(sizeof(Node));//建立一个头结点
H->next=NULL;//建立空的单链表H
}
/*2、建立单链表*/
/*2.1头插法建表*/
void CreateList(LinkList H)
{
Node *s;//
char c;//输入元素变量
int flag=1;//设置一个不等于0的初值,使得循环开始
while(flag)
{
c=getchar();//输入元素,开始建表
if(c!='\n')//设置输入回车循环结束
{
s=(Node*)malloc(sizeof(Node));//建立新结点
s->data=c;
s->next=H->next;
H->next=s;
}
else flag=0;
}
}
/*2.2尾插法建表*/
void GreateFromTail(LinkList H)
{
Node *r,*s;
int flag=1;
char c;
r=H;
printf("请开始输入元素(输入'\\n'结束):\n**格式如:123ad**\n");
while(flag)
{
c=getchar();
if(c!='\n')
{
s=(Node*)malloc(sizeof(Node));
s->data=c;
r->next=s;
r=s;
}
else
{
flag=0;
r->next=NULL;
}
}
}
/*3、删除操作*/
int DelList(LinkList L,int i)//i为被删除元素位置
{
Node*p,*r;
int k;
p=L;
k=0;
while((p->next!=NULL)&&(k<i-1))
{
p=p->next;
k=k+1;
}
if(k!=i-1)
{
printf("删除的位置不合理!\n");
return(ERROR);
}
r=p->next;
p->next=p->next->next;
//free(r);//释放被删除的结点所占的空间
return(OK);
}
/*4、插入操作*/
int InsList(LinkList L,int i,char e)//i为插入位置,e为插入元素
{
Node *pre,*s;
int k;
pre=L;
k=0;
while((pre->next!=NULL)&&(k<i-1))
{
pre=pre->next;
k=k+1;
}
if(k!=i-1)
{
printf("插入位置不合理!\n");
return(-1);
}
s=(Node*)malloc(sizeof(Node));
s->data=e;
s->next=pre->next;
pre->next=s;
return(0);
}
main()
{
int x;
char y,a;//x为插入元素变量,y为插入元素位置变量,a为被删除元素位置变量
LinkList H;
H=(Node*)malloc(sizeof(Node));//头结点
H->next=NULL;//空的单链表H
/*开始向链表输入元素*/
GreateFromTail(H);//CreateList(H)头插法
Display(H);
/*插入操作*/
printf("请输入删除元素位置:");
scanf("%d",&a);
DelList(H,a);
Display(H);
/*插入操作*/
printf("请输入插入元素位置和插入元素(格式如:2 2):");
scanf("%d %c",&x,&y);
InsList(H,x,y);
Display(H);
}
问题总结:
(1)在使用变量的时候,要注意赋初值,比如 H->k =0; 如果去掉,变量k就不能进行使用【平时注意,因为在有些时候不用也行】,
(2)定义函数名字时尽量简单【小错误,尽量注意】
(3)在验收程序时,尽量多试几遍。比如,在写本文代码时,删除和插入操作的函数,大部分是书上的,但如果照搬的话,还是会因为条件判断的问题,出现Bug。
(4)在与同学交流时,大部分可以说一定会犯的是数据类型转换,“cannot…stuct Node** to Node*…“”
2、稍加修改与优化
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int date;
struct Node* next;
int k;//下标
}Node;
Node* CreateL(Node* H)//【头插法建表】
{
int i,n;
printf("请确定输入元素的个数为:\n");
scanf("%d",&n);
printf("请向链表中输入%d个元素:",n);
for( i=0; i<n; i++)
{
Node *p = (Node*)malloc(sizeof(Node));
scanf("%d", &p->date);
p->next = H->next;
H->next = p;
H->k ++;
}
return H;
}
void Display(Node* H)//【输出链表中的元素】
{
Node *p = H->next;
printf("链表元素如下:\n");
while(p)
{
printf("%d ", p->date);
p = p->next;
}
printf("\n");
}
Node *DelL(Node *H)//【删除】
{
Node *p,*r;
int k=0,i;
p=H;
printf("请输入删除位置:");
scanf("%d",&i);
if (i<1 || i>H->k )
{
printf("删除结点的位置i不合理\n");
return 0;
}
while(k<H->k-i)
{
p=p->next;
k=k+1;
}
r=p->next;
p->next=p->next->next;
H->k --;
free (r);
return H;
}
Node *InsL(Node *H)//【插入】
{
Node *pre,*s;
int k=0,i,e;
pre=H;
printf("请输入插入元素地址和插入元素(格式如:3 3):");
scanf("%d %d",&i,&e);
if (i<1 || i>H->k+1 )
{
printf("插入结点的位置i不合理\n");
return 0;
}
while (pre->next!=NULL && k < H->k-i+1)
{
pre=pre->next ;
k=k+1;
}
s=(Node *)malloc(sizeof(Node));
s->date =e;
s->next =pre->next ;
pre->next =s;
H->k++;
return H;
}
main()
{
Node *H;
int a;
H = (Node*)malloc(sizeof(Node));//头结点
H->next = NULL;//空的单链表H
H->k =0;//赋初值
printf(" 欢迎使用链表小李1.0版\n");
CreateL(H);
Display(H);
printf("**选择:输入1 ->删除,输入2-> 插入操作,输入3->退出**\n");
while (a!=3)
{
printf("请用户做出选择:");
scanf("%d",&a);
switch (a)
{
case (1):
{
DelL(H);
Display(H);break;
}
case (2):
{
InsL(H);
Display(H);break;
}
case (3):break;
default : printf("输入失效,请重新输入!!!");
}
printf("\n ");
}
printf("**已退出,感谢您的使用\n**");
}