学数据结构(二)线性表(链式存储)(一些非常基础的概念和算法)

目录

一  线性表的链式存储

☺单链表

☺单链表上的基本运算:

建立单链表

头插法建表:

尾插法建表

单链表查找

按序号查找

 按值查找

单链表插入操作

单链表删除

求单链表的长度

求两个有序单链表的合并

二 循环链表

三 双向链表

双向链表的前插操作

双向链表的删除操作

四 静态链表

静态链表的插入和删除操作示例

静态链表初始化:

 静态链表分配结点与结点回收

五 顺序表和链表的比较

六 总结


(有相关的知识点还会继续更新,比较偏基础,以及很全了,这些先去搞懂)

(学习从一点一滴,坚持从每时每刻)

一  线性表的链式存储

链表定义:         

采用链式存储结构的线性表称为链表 。 现在我们从两个角度来讨论链表:

1.从实现角度看,链表可分为动态链表和静态链表;

2.从链接方式的角度看,链表可分为单链表、循环链表和双链表。

☺单链表

 结点(Node)为了正确地表示结点间的逻辑关系,必须在存储线性表的每个数据元素值的同时,存储指示其后继结点的地址(或位置)信息,这两部分信息组成的存储映象叫做结点(Node)。    

单链表:链表中的每个结点只有一个指针域,我们将这种链表称为单链表。      

单链表包括两个域:数据域用来存储结点的值;指针域用来存储数据元素的直接后继的地址(或位置)。

头指针 :指向链表头结点的指针。

单链表的示例图:

带头结点的单链表示意图

 有时为了操作的方便,还可以在单链表的第一个结点之前附设一个头结点。

单链表的存储结构描述

typedef struct Node    / * 结点类型定义 * /
{ ElemType data;
  struct Node  * next;
}Node, *LinkList;/* LinkList为结构指针类型*/

☺单链表上的基本运算:

建立单链表

头插法建表:

算法描述:从一个空表开始,重复读入数据,生成新结点,将读入数据存放到新结点的数据域中,然后将新结点插入到当前链表的表头结点之后,直至读入结束标志为止。

 

Linklist  CreateFromHead()
{   LinkList   L;  Node   *s; int flag=1;
 /* 设置一个标志,初值为1,当输入“$”时,flag为0,建表结束*/
 L=(Linklist)malloc(sizeof(Node));/*为头结点分配存储空间*/
 L->next=NULL;
 while(flag)
 {   c=getchar();   
     if(c!=’$’) /*为读入的字符分配存储空间*/
     {  s=(Node*)malloc(sizeof(Node)); s->data=c;
       s->next=L->next; L->next=s;      }
     else	flag=0;
  }
} 

尾插法建表

尾插法建表算法

Linklist  CreateFromTail() /*将新增的字符追加到链表的末尾*/
{  LinkList L;  Node *r, *s;  int   flag =1; 
   L=(Node * )malloc(sizeof(Node));/*为头结点分配存储空间*/
   L->next=NULL; r=L;   /*r指针始终动态指向链表的当前表尾*/
   while(flag)/*标志,初值为1。输入“$”时flag为0,建表结束*/
   { 	c=getchar();
	    if(c!=’$’)
   {  s=(Node*)malloc(sizeof(Node)); s->data=c;
     r->next=s;   r=s  }
   else {  flag=0;  r->next=NULL; }
  }
}

单链表查找

按序号查找

算法描述:设带头结点的单链表的长度为n,要查找表中第i个结点,则需要从单链表的头指针L出发,从头结点(L->next)开始顺着链域扫描,用指针p指向当前扫描到的结点,初值指向头结点(pL->next),用j做记数器,累计当前扫描过的结点数(初值为0),当j = i 时,指针p所指的结点就是要找的第i个结点 。算法实现,算法演示。

按序号查找算法实现

/ * 在带头结点的单链表L中查找第i个结点,若找到(1≤i≤n),则返回该结点的存储位置;  否则返回NULL * /
Node *  Get(LinkList  L, int i)
{  Node  *p;
   p=L;j=0;       / * 从头结点开始扫描 * /
   while ((p->next!=NULL)&&(j<i)
   { p=p->next; j++;   / * 扫描下一结点 * /
   }        / * 已扫描结点计数器 * /
   if(i= =j)return p;   / * 找到了第i个结点 * /
   else   return NULL;   / * 找不到,i≤0或i>n * /
}

 按值查找

算法描述:按值查找是指在单链表中查找是否有结点值等于e的结点,若有的话,则返回首次找到的其值为e的结点的存储位置,否则返回NULL。查找过程从单链表的头指针指向的头结点出发,顺着链逐个将结点的值和给定值e作比较。算法实现,算法演示。

/ * 在带头结点的单链表L中查找其结点值等于key的结点,若找到则返回该结点的位置p,否则返回NULL * /
Node *Locate( LinkList L,ElemType key)
{  Node *p;
   p=L->next;   / * 从表中第一个结点比较 * /
   while (p!=NULL)
     if (p->data!=key)
     	    p=p->next;     
     else  break;     / * 找到结点key,退出循环 * /
   return p;
}

单链表插入操作

算法描述:要在带头结点的单链表L中第i个数据元素之前插入一个数据元素e,需要首先在单链表中找到第i-1个结点并由指针pre指示,然后申请一个新的结点并由指针s指示,其数据域的值为e,并修改第i-1个结点的指针使其指向s,然后使s结点的指针域指向第i个结点。

 单链表插入操作算法实现

void InsList(LinkList L,int i,ElemType e)
{  /*在带头结点的单链表L中第i个结点之前插入值为e的新结点。 */
	 Node *pre,*s;  pre=L; int  k=0;
  while(pre!=NULL&&k<i-1)  
  /*先找到第i-1个数据元素的存储位置,使指针Pre指向它*/ 
	 { pre=pre->next;	k=k+1;     }
  if(k!=i-1)      
  { printf(“插入位置不合理!”);  return; }
  s=(Node*)malloc(sizeof(Node));  /*为e申请一个新的结点*/
  s->data=e;            /*将待插入结点的值e赋给s的数据域*/
  s->next=pre->next; pre->next=s;
} 

单链表删除

算法描述:欲在带头结点的单链表L中删除第i个结点,则首先要通过计数方式找到第i-1个结点并使p指向第i-1个结点,而后删除第i个结点并释放结点空间。

 单链表删除算法实现

void DelList(LinkList L,int i,ElemType *e)
/*在带头结点的单链表L中删除第i个元素,并保存其值到变量*e中*/
{  
	Node *p,*r; p=L; int k =0;
	while(p->next!=NULL&&k<i-1) /*寻找被删除结点i的前驱结点*/
  { p=p->next; k=k+1; }
if(k!=i-1)    /* 即while循环是因为p->next=NULL而跳出的*/
{  printf(“删除结点的位置i不合理!”); return ERROR; }
r=p->next; p->next=p->next->next    /*删除结点r*/
free(r);    /*释放被删除的结点所占的内存空间*/
}

求单链表的长度

算法描述:可以采用“数”结点的方法来求出单链表的长度,用指针p依次指向各个结点,从第一个元素开始“数”,一直“数”到最后一个结点(p->next=NULL)。

int	ListLength(LinkList L) /*L为带头结点的单链表*/
  {   Node *p; p=L->next;	
  j=0;   /*用来存放单链表的长度*/
  while(p!=NULL)
  {	p=p->next; j ++; }
  return j;
}

求两个有序单链表的合并

有两个单链表LA和LB,其元素均为非递减有序排列,编写一个算法,将它们合并成一个单链表LC,要求LC也是非递减有序排列。要求:利用新表LC利用现有的表LA和LB中的元素结点空间,而不需要额外申请结点空间。例如LA=(2, 2, 3), LB=(1, 3, 3, 4), 则LC=(1, 2, 2, 3, 3, 3, 4)。

【算法描述】要求利用现有的表LA和LB中的结点空间来建立新表LC,可通过更改结点的next域来重建新的元素之间的线性关系,为保证新表仍然递增有序,可以利用尾插入法建立单链表的方法,只是新建表中的结点不用malloc,而只需要从表LA和LB中选择合适的点插入到新表LC中即可。

LinkList  MergeLinkList(LinkList LA, LinkList LB)
/*将递增有序的单链表LA和LB合并成一个递增有序的单链表LC*/
{   Node *pa,*pb;
    LinkList LC;
/*将LC初始置空表。pa和pb分别指向两个单链表LA和LB中的第一个结点,r初值为LC*/
    pa=LA->next;
    pb=LB->next;
 LC=LA;
LC->next=NULL;r=LC;							/*初始化操作*/

/*当两个表中均未处理完时,比较选择将较小值结点插入到新表LC中。*/
 while(pa!=NULL&&pb!=NULL)
		 {if(pa->data<=pb->data)
{r->next=pa;r=pa;pa=pa->next;}
else
{r->next=pb;r=pb;pb=pb->next;}
}
if(pa) /*若表LA未完,将表LA中后续元素链到新表LC表尾*/
r->next=pa;
else	 /*否则将表LB中后续元素链到新表LC表尾*/
r->next=pb;
free(LB);
return(LC);
 }  /* MergeLinkList */

二 循环链表

循环链表(Circular Linked List) 是一个首尾相接的链表。

  特点:将单链表最后一个结点的指针域由NULL改为指向头结点或线性表中的第一个结点,就得到了单链形式的循环链表,并称为循环单链表。在循环单链表中,表中所有结点被链在一个环上。带头结点循环单链表示意图。

     已知:有两个带头结点的循环单链表LA、LB,编写一个算法,将两个循环单链表合并为一个循环单链表,其头指针为LA。     

算法思想:先找到两个链表的尾,并分别由指针p、q指向它们,然后将第一个链表的尾与第二个表的第一个结点链接起来,并修改第二个表的尾Q,使它的链域指向第一个表的头结点。

LinkList   merge_1(LinkList LA,LinkList LB)
/*此算法将两个链表的首尾连接起来*/
{  Node *p, *q; p=LA; q=LB;
 while (p->next!=LA) p=p->next;	/*找到表LA的表尾*/
 while (q->next!=LB) q=q->next;	/*找到表LB的表尾*/
 q->next=LA;/*修改表LB 的尾指针,使之指向表LA 的头结点*/
 p->next=LB->next;/*修改表LA的尾指针,使之指向表LB 中的第一个结点*/
	free(LB); 
return(LA);
}

三 双向链表

  双向链表:在单链表的每个结点里再增加一个指向其前趋的指针域prior。这样形成的链表中就有两条方向不同的链,我们称之为双 ( 向) 链表 (Double Linked List)。

双向链表的结构定义:   

	typedef struct Dnode
	{   ElemType data;
		   struct DNode *prior,*next;
	 } DNode,	* DoubleList;

 双链表的结点结构

双向循环链表示意图

双向链表的前插操作

算法描述:欲在双向链表第i个结点之前插入一个的新的结点,则指针的变化情况如图所示。

 双向链表的前插操作算法实现

void DlinkIns(DoubleList L,int i,ElemType e)
	{ DNode  *s,*p;
	  … /*首先检查待插入的位置i是否合法*/
	  … /*若位置i合法,则让指针p指向它*/
  s=(DNode*)malloc(sizeof(DNode));
  if (s)
   {  s->data=e;
      s->prior=p->prior; p->prior->next=s;
      s->next=p; p->prior=s; return TRUE;
  }  else return FALSE;
}   算法演示连接。

双向链表的删除操作

算法描述:欲删除双向链表中的第i个结点,则指针的变化情况如图所示。

 

 双向链表的删除操作算法实现:

int	DlinkDel(DoubleList L,int i,ElemType *e)
	{	DNode  *p;
		… /*首先检查待插入的位置i是否合法*/
		… /*若位置i合法,则让指针p指向它*/
  	*e=p->data;
  	p->prior->next=p->next;
  	p->next->prior=p->prior;
  	free(p);
   	return TRUE;
   } 

四 静态链表

基本概念:

游标实现链表的方法:定义一个较大的结构数组作为备用结点空间(即存储池)。当申请结点时,每个结点应含有两个域:data域和next域。data域存放结点的数据信息,next域为游标指示器,指示后继结点在结构数组中的相对位置(即数组下标)。数组的第0个分量可以设计成表的头结点,头结点的next域指示了表中第一个结点的位置,为0表示静态单链表的结束。我们把这种用游标指示器实现的单链表叫做静态单链表(Static Linked List)。静态链表的结构定义,静态链表的插入和删除操作示例。

基本操作:

初始化、分配结点与结点回收、前插操作、删除。

静态链表的结构定义

#define  Maxsize= 链表可能达到的最大长度
typedef  struct
{
  ElemType data;
  int cursor;
   }Component, StaticList[Maxsize]; 

静态链表的插入和删除操作示例

静态链表初始化:

算法描述:初始化操作是指将这个静态单链表初始化为一个备用静态单链表。设space为静态单链表的名字,av为备用单链表的头指针,其算法如下:

void initial(StaticList space,int *av)
{  int k;
   space[0]->cursor=0;/*设置静态单链表的头指针指向位置0*/
   for(k=0;k<Maxsize-1;k++)
  	space[k]->cursor=k+1;    /*连链*/
   space[Maxsize-1] . cursor =0;    /*标记链尾*/
   *av=1;  /*设置备用链表头指针初值*/
  }	 

 静态链表分配结点与结点回收

分配结点

int getnode(int *av)/*从备用链表摘下一个结点空间,分配给待插入静态链表中的元素*/
{  int i=*av; 
   *av=space[*av].cur;  
   return i;   
}

结点回收

void   freenode(int *av,int k) /*将下标为 k的空闲结点插入到备用链表*/
{  space[k].cursor=*av;  *av=k; }

 五 顺序表和链表的比较

1.基于空间的考虑

2.基于时间的考虑

3.基于语言的考虑

六 总结

 主要知识点

1、线性表的特征:

线性表中每个数据元素有且仅有一个直接前驱和一个直接后继,第一个结点无前驱,最后一个结点无后继。

2、线性表存储方式:

线性表顺序存储(顺序表):采用静态分配方式,借助于C语言的数组类型,申请一组连续的地址空间,依次存放表中元素,其逻辑次序隐含在存储顺序之中

线性表链式存储(链表):采用动态分配方式,借助于C语言的指针类型,动态申请与动态释放地址空间,故链表中的各结点的物理存储可以是不连续的。当表长度变化时仅需适当变化指针的联接,适合于表中元素个数动态变化。

3、单链表的操作特点:

⑴顺链操作技术  

⑵指针保留技术

4、链表处理中的相关技术

(有相关的知识点还会继续更新,比较偏基础,以及很全了,这些先去搞懂)

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

登登登__

期待每一份真诚

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值