数据结构之线性表——(二、链式存储结构)[c语言]

数据结构之线性表——(二、链式存储结构-单链表)

链式存储结构以及基本运算的实现

背景:由于线性表的存储特点是用物理上的相邻实现逻辑上的相邻,他要求用连续的存储单元顺序存储线性表中的各个元素,所以,对线性表中的元素进行插入、删除时要移动元素,很影响运算的效率。而在链式存储结构中,存储单元的地址可连续,也可不连续,这就意味着这些数据元素可以存放在内存中未被占用的任何位置。它不要求逻辑上相邻的两个数据元素物理上也相邻,其逻辑关系是通过“链”建立起数据元素之间的关系,因此对线性表的插入、删除操作时不需要移动数据,提高了效率。

一、单链表

单链表的表示

链表是通过一组任意的存储单元来存储线性表中的数据元素的,每个数据元素(ai),除了存放自身的数据外,还需要存放其后继ai+1所在的存储单元的地址,这两部分信息组成一个“结点”,存放数据元素信息的数据域为data,存放其后继地址的称为指针域next,所以n个数据元素的线性表通过每个结点的指针域连成一条“链”,故称为链表。其结点的结构如下:
在这里插入图片描述
注:在这里我们称为单链表,单链表是因为其每个结点中只有一个指向后继的指针,所以称为单链表。

1、单链表的定义

单链表是由一个个结点构成的,使用单链表首先要定义一个结点,也可称为结构体,其定义如下:

typedef struct node
{
DataType data;
struct node *next;
}Lnode,*LinkList;  
/*Lnode是结构体别名,用其定义结点如:Lnode n;
但是用  *LinkList定义的是指针。 */

定义头指针变量

LinkList H;           //linkList是指向Lnode类型结点的指针类型 

在这里插入图片描述
  在上图中,顺序存储结构的地址是连续的,而且其数据域中的数据也是按照逻辑结构自上而下的。在链式存储结构中,每个结点(node)不仅存储了数据本身(A),还存放了下一个结点的地址(0x5),但头指针中没有存放数据,它存放的是第一个结点(A)的地址。在顺序存储结构里我们将A称为B的直接前驱,B称为A的直接后继,由于是单向链表,在这个链式存储中只有前驱到后继的指针,前驱和后继的关系由指针来链接,也就是说,如果我们要查找后面的元素必须要从头查找。
  作为线性表的一种存储结构,我们关心的是结点之间的逻辑结构,而对每个结点之间的实际地址不感兴趣。
  单链表分为带头和不带头两种
  在这里插入图片描述
在这里插入图片描述
对于线性表来说,有头有尾,链表也同样,我们把链表中第一个结点存储的位置叫做头指针,通常用“头指针”来标识一个单链表。在上图带头结点单链表示意图中,第一个结点的地址放在了指针变量H中,链表的最后一个结点指针为空(NULL)。
在下图中,假设p是指向线性表第i个元素的指针,则结点ai的数据域用p->data来表示,即p->data的值是一个数据元素,p->data = ai;结点ai的指针域用p->next表示,即p->next是一个指针,(p->next)->data = ai+1
在这里插入图片描述

2、单链表的基本操作

(1)初始化创建单链表
在每次将新节点插入到单链表的尾部时,我们需要加入一个指针 r来始终指向单链表中的为节点。
步骤:
1、初始化,头指针H=NULL,尾指针r=NULL
2、线性表中元素的顺序依次读入数据元素,如果不是结束标志,申请结点。
3、将新节点插入到r所指结点的后面,然后r指向新节点。(第一个结点不同)

LinkList Creat_LinkList()
{ 
    LinkList L = NULL;    //头指针L置空
    Lnode *s,*r=NULL;
    char x,flag='0';
    printf("请输入数据,输入0结束\n");
    scanf("%d",&x);
    while(x!=flag)
    { 
        s = (Lnode *)malloc(sizeof(Lnode));  //申请一个新结点
        s->data = x;      //将输入的x放在结点的数据域中
        if(L==NULL) L = s;    //如果头指针为空,将结点s的指针放在头指针中
        else r->next = s;     //若不为空放在指针r 中
        r = s;               //r始终指向最后一个结点
        printf("请输入数据,输入0结束\n");
        scanf("%c",&x);
     }
     if(r!=NULL)
     r->next = NULL;    //对于非空表,最后结点的指针域放空指针
     return L;
     }

(2)按序号查找结点
1、从链表的第一个数据元素结点起,判断当前结点是否是第i个结点。
2、若是第i个结点,则返回,该结点的指针,否则继续下一个,直至结束为止。
3、若没有第i个结点时返回为空。

//按序号查找结点
Lnode *Get_LinkList(LinkList L,int i)
{
     Lnode *p =L;                  //把第一个结点的指针放在p里面
	 int j=1;
	 if(i==1) return p;
     if(p==NULL) { printf("表为空");return NULL; }
     while((p->next!=NULL) && j<i)   //判断当前结点是否是第i个结点
     {  p = p->next; ++j;}
     if(j==i)    return p;         //如果是第i个结点,则返回该节点的指针
     else        return NULL;
 }

(3)单链表的插入
  设p指向单链表中某结点,s指向待插入的值为x的新节点,将结点s插入到结点p的后面,插入的示意图如下:
  在这里插入图片描述

  1. s->next = p->next;
  2. p->next = s; 先挂链再改链,顺序不能交换

1、查找第i-1个结点,如果找到继续,否则结束
2、申请、填装新节点。
3、将新节点插入,结束。

Lnode * Insert_LinkList(LinkList L,int i,char x)
{
     Lnode *p,*s;  //定义p s 两个指针
     
     if(i==1)             //如果插入的位置是第一个呢
     {  s = (Lnode *)malloc(sizeof(Lnode));
        s->data = x;
        s->next = L;    //把老的第一个的地址放进新第一个里面
        L = s;  return L;//把新第一个的地址放在头指针里面
      }
	 
	 p = Get_LinkList(L,i-1);
      if( p==NULL)        //第i个结点前面位置不存在不能插入
      {    printf("插入位置前面没有元素!\n");
           return 0;
      }
      else                //在插入位置前面一个元素的地址放在新结点中
      {
          s = (Lnode *)malloc(sizeof(Lnode));//创建新节点来存放插入的元素
          s->data = x;            //将用户要插入的元素放入数据域中
          s->next = p->next;     //p->next是i-1的地址,s放在i位置的前面,接在i-1位置后面
          p->next = s;
          return L;
       }
	 
 }

在插入函数中,如果我们在插入在第i个位置,那就查找第i-1个位置,如果i-1个位置不存在就无法插入,当查找到i-1个位置时,将i-1个结点中存储的第i个位置的地址放在新申请结点s的指针域中,再将s的地址放在i-1个位置的指针域中,这样s结点就插入到位置i中,i接在s的后面。
  (4)删除结点
  设p指向单链表中某结点,现在要根据用户输入的位置i 删除结点p。
  要删除结点p,首先要找到p的前驱结点q,然后完成指针的操作。
  在这里插入图片描述

  1. q-next = p->next;
  2. free(q);

1、查找到第 i-1个结点,如果找到继续,否则结束
  2、若存在第i个结点,继续下一步,否则返回没找到
  3、删除这个结点

//按位置删除结点
LinkList Del_LinkList(LinkList L,int i)
{  LinkList p;Lnode *s =L; 
   p = Get_LinkList(L,i-1);
   if(i==1)
   {      
			L = s->next;
			return L;
   }
   else if(p->next == NULL){ printf("第%d个结点不存在\n",i); return L; }     
   else{
		 s = p->next;
		 p->next = s->next;
		 free(s);
		 return L;
		  }
}

在这里插入图片描述

  • 在单链表中插入、删除一个结点,必须知道其前驱结点
  • 单链表不具有按序号随机访问的特点,只能从头指针开始一个又一个顺序进行
      实现单链表基本的创建、插入、删除等操作:
#include "stdio.h"
#include "malloc.h"
#include "stdlib.h"
typedef struct node
{  char data;
   struct node *next;
}Lnode,*LinkList;

//单链表的创建
LinkList Creat_LinkList()
{   LinkList L = NULL;
	Lnode *s,*r=NULL;
    char flag='0';
	char x;
	printf("请输入,输入0结束\n");
	fflush(stdin);
	scanf("%c",&x);
	while(x!=flag)
	{   s = (Lnode *)malloc(sizeof(Lnode));
	    s->data =x;
	    if(L==NULL) L=s;
		else r->next = s;
		r = s;
		scanf("%c",&x);
	}
	if(r!=NULL)
	r->next = NULL;
	return L;
}

void Disp_LinkList(LinkList L)
{  LinkList p = L;
   while(p!=NULL)
   { printf("%c ",p->data);p=p->next;  }
}

//按序号查找结点
Lnode *Get_LinkList(LinkList L,int i)
{
     Lnode *p =L;                  //把第一个结点的指针放在p里面
	 int j=1;
	 if(i==1) return p;
     if(p==NULL) { printf("表为空");return NULL; }
     while((p->next!=NULL) && j<i)   //判断当前结点是否是第i个结点
     {  p = p->next; ++j;}
     if(j==i)    return p;         //如果是第i个结点,则返回该节点的指针
     else        return NULL;
 }


Lnode * Insert_LinkList(LinkList L,int i,char x)
{
     Lnode *p,*s;  //定义p s 两个指针
     
     if(i==1)             //如果插入的位置是第一个呢
     {  s = (Lnode *)malloc(sizeof(Lnode));
        s->data = x;
        s->next = L;    //把老的第一个的地址放进新第一个里面
        L = s;  return L;//把新第一个的地址放在头指针里面
      }
	 
	 p = Get_LinkList(L,i-1);
      if( p==NULL)        //第i个结点前面位置不存在不能插入
      {    printf("插入位置前面没有元素!\n");
           return 0;
      }
      else                //在插入位置前面一个元素的地址放在新结点中
      {
          s = (Lnode *)malloc(sizeof(Lnode));//创建新节点来存放插入的元素
          s->data = x;            //将用户要插入的元素放入数据域中
          s->next = p->next;     //p->next是i-1的地址,s放在i位置的前面,接在i-1位置后面
          p->next = s;
          return L;
       }
	 
 }
//按位置删除结点
LinkList Del_LinkList(LinkList L,int i)
{  LinkList p;Lnode *s =L; 
   p = Get_LinkList(L,i-1);
   if(i==1)
   {      
			L = s->next;
			return L;
   }
   else if(p->next == NULL){ printf("第%d个结点不存在\n",i); return L; }     
   else{
		 s = p->next;
		 p->next = s->next;
		 free(s);
		 return L;
		  }
}

void main()
{
	LinkList L; int n;char x;
	L = Creat_LinkList();
	printf("你创建的单链表为:\n");
	Disp_LinkList(L);
	printf("\n输入你要插入的位置:\n");
	scanf("%d",&n);fflush(stdin);
	printf("输入你要插入的元素:\n");
	scanf("%c",&x);
	L = Insert_LinkList(L,n,x);
	printf("你插入后的单链表为:\n");
    Disp_LinkList(L); 
	printf("\n输入你要删除的位置:\n");
    scanf("%d",&n);
	L = Del_LinkList(L,n);
	printf("你删除后的单链表为:\n");
    Disp_LinkList(L);
}

代码运行图:
在这里插入图片描述
  单链表的简单运用:使用单链表制作电子通讯录(单链表)
  需求:

  1. 输入联系人信息,包含姓名、电话号码两部分。
  2. 实现联系人信息的插入:输入要插入的位置和新的联系人信息,将此联系人插入的链表的相应位置。
  3. 实现联系人信息的删除:输入要删除的联系人姓名,将此姓名的联系人删除。
  4. 实现按联系人的姓名查找信息
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
struct numbook              //定义一个结构体
{   char name[10];          //定义一个数组来存放姓名
    char phone[15];         //定义一个数组来存放电话号码
   struct numbook * next;   //定义一个指针
};

//判断用户输入的号码是否合法
int IsLegal(const char *str)   //用常量输入,只能使用无法修改
{
	while(*str!='\0')         //!='\0'.就是运行到字符串结尾时结束。
	{
		if(!(*str>='0' && *str<='9'))  //用户输入的号码不在0到9之间返回0
		return 0;
		str++;
	}
	return 1;
}

//用户信息导入
 numbook * input()
 {  
	 numbook *p,*q=NULL;
	 numbook * L = NULL;
	 char name[10],phone[15];
     while( printf("请输入姓名和电话号码,输入“ok”结束!\n"),scanf("%s",&name),strcmp(name,"ok")!=0)
   { 
	   p = (struct numbook *)malloc(sizeof(numbook));
	    strcpy(p->name,name);   //将用户输入的姓名赋值到单链表中的p-name中
		while(scanf("%s",&phone),IsLegal(phone)==0)
		{	printf("电话号码无效,重新输入。\n");}      //判断电话号码是否合法

			strcpy(p->phone,phone);

			if( L == NULL) {  L = p; }

	        else {  q->next = p; }

	    	q = p;
	}
        if(q!=NULL) q->next =NULL;  return L;
}
//输出电话簿
void display(numbook * L)
{   while(L!=NULL)
{
	printf("%s - %s \n",L->name,L->phone);
	L = L->next;
}
}
//在电话簿中根据姓名查找联系人信息
numbook * find(numbook *L,char *na)
{
	while(L!=NULL)
	{ if(strcmp(L->name,na)==0)     //如果姓名和电话簿中的相等返回L
	    return L;                 
    	else  
		L = L->next;          //否则接续下一个结点
	}

	return NULL;
}
//删除联系人
numbook * DeleteByName(numbook *L,const char *na)
{    numbook *p,*q;  //定义两个指针,让q和p同时前进,便于操作指针
    if(L==NULL){  printf("空表无法再删除!\n");     //当L为空表时
                   return L;  
	}
	if(strcmp(L->name,na)==0)        //当删除的是第一个结点时
	{
	  p = L; L = L->next; 
	  free(p);printf("删除成功!\n"); return L;
	}
	  p = L;           //将电话簿的头指针放在p中,p指向了第一个元素
	  q = p->next;     //将第一个结点存放的指针放在q中,q指向了第二个结点
     while(q!=NULL && strcmp(q->name,na)!=0)
	 { 
		p = q;
		q = q->next;
	 }
	if(q->next==NULL){   
	    free(q); 
	    p->next = NULL;printf("删除成功!\n");return L; }
	    //删除的元素为最后一个,q结点的指针是空的,直接释放q再将q前面的p指针置空
	else { 
	   p->next = q->next; free(q);printf("删除成功!\n");return L; }
	  return L;   //将q中存放的地址放到q前面位置的p中,再释放q
}

//插入联系人信息
numbook * InsertByName(numbook *L,const char *na,numbook *e)
{
	numbook *p,*q;  //同样定义两个指针,让q和p同时前进,便于操作指针
	if(L==NULL)	{   L = e;  e->next = NULL;  return L;	} 
	//如果头指针为空,代表输入的为第一个结点,就把它地址放在头指针里
	 //在把这个结点的指针域置空,代表后面没有结点了,也就是它为第一个结点
	if(strcmp(L->name,na)==0)   //当用户输入的姓名是第一个联系人的姓名时
	{	e->next = L;  L = e;  return L;	}  
	  //将这第一个人的地址放在新插入结点的指针域中,再将它的指针放在头指针中
		p = L; q = p->next;
		while(q!= NULL && strcmp(q->name,na)!=0)
		{
			p = q; q = q->next;
		}
         
		if(q == NULL)
		{ 	p->next = e; 
			e->next = NULL;
			return L;
		}
		else {
			p->next = e;   e->next = q;  return L;
		}
}
void main(){
	char na[10];int i=0;
    numbook * L,*p,*e;
	L = input();
	display(L);
	while(i<10){
		fflush(stdin);
	while(printf("请输入你要操作的功能(1.查询联系人。2.插入联系人。3.删除联系人)\n"),scanf("%d",&i),i>3)
	{printf("输入错误,重新输入");}
	switch(i){
	case 1: 	printf("请输入要查找的姓名:\n");
             	fflush(stdin);   //清除键盘缓冲区
                scanf("%s",&na);
            	p = find(L,na);
            	if(p==NULL) printf("查无此人\n");
	            else printf("这个人的信息为:姓名“%s”,电话“%s”。\n",p->name,p->phone);
				break;

	case 2:     printf("请输入要插入的信息(姓名  电话)\n");
               	e = (struct numbook *)malloc(sizeof(struct numbook));
            	scanf("%s%s",e->name,e->phone);
            	printf("请输入要插入位置的姓名:\n");
            	scanf("%s",na);
            	L = InsertByName(L,na,e);
            	display(L);
				break;

	case 3:     printf("请输入要删除联系人的姓名:\n");
		        scanf("%s",&na);
				p = find(L,na);
            	if(p==NULL) printf("查无此人\n");
		        L = DeleteByName(L,na);
				display(L);
		        break;
				}
	            i=NULL;
	}
	}

运行结果:
在这里插入图片描述

发布了3 篇原创文章 · 获赞 0 · 访问量 102

猜你喜欢

转载自blog.csdn.net/weixin_44924446/article/details/102977616