学习笔记:2.3 线性表的链式表示和实现(单链表详解)

2.3 线性表的链式表示和实现

(注意:所有代码均已成功测试。编译环境:devC++)

(一)复习:(线性顺序存储结构的优缺点)

优点:

  1. 无须为表示表中元素之间的逻辑关系而增加额外的存储空间。

  2. 可以迅速地存取表中任一位置的元素。

缺点:

  1. 插入和删除操作需要移动大量元素。(耗费时间)

  2. 当线性表长度变化较大时,难以确定存储空间的容量。

  3. 造成存储空间的 “碎片”。

(二)线性表的链式存储结构(链表)

1. 单链表

课本概念:用一组地址任意的存储单元存放线性表中的数据元素。

元素(数据元素的映象) + 指针(指示后继元素存储位置)= 结点 (表示数据元素 或 数据元素的映象)

以“结点的序列”表示线性表称作链表

在这里插入图片描述

以线性表中第一个数据元素 的存储地址作为线性表的地址,称作线性表的头指针。

有时为了操作方便,在第一个结点之前虚加一个“头结点”,以指向头结点的指针为链表的头指针。

线性表为空表时,头结点的指针域为空。

在这里插入图片描述


理解(要点):

  • 链表是一种常用的数据结构,它通过指针将一些列数据结点,连接成一个数据链。相对于数组,链表具有更好的动态性(非顺序存储)。

在这里插入图片描述

  • 数据域用来存储数据,指针域用于建立与下一个结点的联系。

  • 建立链表时无需预先知道数据总量的,可以随机的分配空间 (链表的内存是非连续的),可以高效的在链表中的任意位置实时插入或删除数据 (只需要修改指针即可)。

  • 链表的开销,主要是访问顺序性和组织链的空间损失(指针域空间的开销)。

简单理解数组和链表的区别:

数组:一次性分配一块连续的存储区域。

  • ​ 优点:随机访问元素效率高。
  • ​ 缺点: 1.需要分配一块连续的存储区域(很大区域,有可能分配失败)。

​ 2.删除和插入某个元素效率低。

链表:无需一次性分配一块连续的存储区域,只需分配n块节点存储区域,通过指针建立关系。

  • ​ 优点:1.不需要一块连续的存储区域。

    ​ 2.删除和插入某个元素效率高。

  • ​ 缺点:随机访问元素效率低。


1.1 结点和单链表的代码描述

结点的定义:

链表的节点类型实际上是结构体变量,此结构体包含数据域和指针域:

  • 数据域用来存储数据;

  • 指针域用于建立与下一个结点的联系,当此节点为尾节点时,指针域的值为NULL;

/* C语言线性表的单链表存储结构 */
typedef struct  LNode {
      ElemType      data;  // 数据域     typedef int ElemType;
      struct LNode   *next;  // 指针域
   } LNode;

typedef struct LNode *LinkList; //定义头指针。

理解:

p->data/p->next/p->next->data;

在这里插入图片描述

小编分析:p是指向线性表第i个元素的指针,则该节点a(i)的数据域可以用p->data来表示,它的值是数据元素,节点a(i)的指针域可以用p->next来表示;p->next指向第i+1个元素,即指向a(i+1)的指针。所以p->next->data即为a(i+1);

链表的初始化:
/* 初始化链式线性表 */
Status InitList(LinkList &L)   //typedef int Status;
{ 
    L=(LinkList)malloc(sizeof(LNode));    //c++: L = new LNode;
    if(!L) // 存储分配失败 
            return ERROR;
    L->next=NULL; //指针域为空 

    return OK;
}
判断链表是否为空:
/* 初始条件:链式线性表L已存在。操作结果:若L为空表,则返回TRUE,否则返回FALSE */
Status ListEmpty(LinkList L)
{ 
    if(L->next)
            return FALSE;
    else
            return TRUE;
}
清空单链表:

(核心思想:工作指针向后移,依次清空,释放内存)

/* 初始条件:链式线性表L已存在。操作结果:将L重置为空表 */
Status ClearList(LinkList &L)
{ 
	LinkList p,q;
	p=L->next;           // p指向第一个结点 
	while(p)           
	{
		q=p->next;
		free(p);
		p=q;
	}
	L->next=NULL;        // 头结点指针域为空
	return OK;
}
返回链表中数据元素的个数:
/* 初始条件:链式线性表L已存在。操作结果:返回L中数据元素个数 */
int ListLength(LinkList L)
{
    int i=0;
    LinkList p=L->next; // p指向第一个结点 
    while(p)                        
    {
        i++;
        p=p->next;
    }
    return i;
}
用e返回L中第i个数据元素的值:

(核心思想:工作指针向后移,遍历搜索)

算法思路:

(1)声明一个指针p指向链表第一个结点,初始化i从1开始;

(2)当j<i时,就遍历链表;让p的指针向后移动,不断指向下一结点,j累加1;

(3)若到链表末尾p为空,则说明第i个结点不存在;

(4)否则查找成功,返回结点p的数据。

/* 初始条件:链式线性表L已存在,1≤i≤ListLength(L) */
Status GetElem(LinkList L,int i,ElemType &e)
{
	int j;
	LinkList p;		// 声明一结点p 
	p = L->next;		// 让p指向链表L的第一个结点 
	j = 1;		//  j为计数器 
	while (p && j<i)  // p不为空或者计数器j还没有等于i时,循环继续 
	{   
		p = p->next;  // 让p指向下一个结点 
		++j;
	}
	if ( !p || j>i ) 
		return ERROR;  //  第i个元素不存在 
	e = p->data;   //  取第i个元素的数据 
	return OK;
}
返回L中第1个与e满足关系的数据元素的位序:

(核心思想:工作指针向后移,遍历链表判断是否符合条件)

int LocateElem(LinkList L,ElemType e)
{
    int i=0;
    LinkList p=L->next;
    while(p)
    {
        i++;
        if(p->data==e) 
                return i;
        p=p->next;
    }

    return 0;
}
单链表的元素插入:

图解:

在这里插入图片描述
在这里插入图片描述
算法思路:

(1)声明一个指针p指向链表头结点,初始化j从1开始。

(2)当j<i时,就遍历链表;让p的指针向后移动,不断指向下一结点,j累加1。

(3)若到链表末尾p为空,则说明第i个结点不存在。

(4)否则查找成功,在系统中生成一个空结点s。

(5)将数据元素e赋值给s->data。

(6)由图解可知单链表的插入标准语句是s->next=p->next; p->next=s; (顺序切不可交换)。

(7)返回成功。

Status ListInsert(LinkList &L,int i,ElemType e)
{ 
	int j;
	LinkList p,s;
	p = L;   
	j = 1;
	while (p && j < i)    
	{
		p = p->next;
		++j;
	} 
	if (!p || j > i) 
		return ERROR;  
	s = (LinkList)malloc(sizeof(LNode));  
	s->data = e;  
	s->next = p->next;      /* 将p的后继结点赋值给s的后继  */
	p->next = s;          /* 将s赋值给p的后继 */
	return OK;
}
单链表的元素删除:

图解:

在这里插入图片描述
将前继结点绕过指向后继结点即可。

算法思路:

(1) 声明一结点p指向链表第一个结点,初始化j从1开始。

(2) 当j<i时,就遍历链表,让p的指针向后移动,不断指向下一个结点,j累加。

(3) 若到链表末尾p为空,则说明第i个元素不存在。

(4) 否则查找成功,将欲删除的结点p->next赋值给q。

(5) 单链表的删除标准语句p->next=q->next。

(6) 将q结点中的数据赋值给e,作为返回。

(7) 释放q结点。

Status ListDelete(LinkList &L,int i,ElemType &e) 
{ 
	int j;
	LinkList p,q;
	p = L->next;
	j = 1;
	while (p && j < i)	
	{
        p = p->next;
        ++j;
	}
	if (!(p->next) || j > i) 
	    return ERROR;           
	q = p->next;
	p->next = q->next;			
	e = q->data;              
	free(q);                   
	return OK;
}
单链表的整表创建:
1. 头插法
/* 建立带表头结点的单链线性表L(头插法) */
void CreateListHead(LinkList &L, int n) 
{
	LinkList p;
	int i;
	L = (LinkList)malloc(sizeof(LNode));
	L->next = NULL;                     
	for (i=0; i<n; i++) 
	{
		p = (LinkList)malloc(sizeof(LNode)); 
		p->data = (i+1)*10;            
		p->next = L->next;    
		L->next = p;					
	}
}
2. 尾插法
/*建立带表头结点的单链线性表L(尾插法) */
void CreateListTail(LinkList &L, int n) 
{
	LinkList p,r;
	int i;                    
	L = (LinkList)malloc(sizeof(LNode)); 
	r=L;                                
	for (i=0; i<n; i++) 
	{
		p = (LinkList)malloc(sizeof(LNode)); 
		p->data = (i+1)*10;          
		r->next=p;                        
		r = p;                           
	}
	r->next = NULL;                       
}
单链表的销毁:
Status DestroyList(LinkList &L){
	LinkList p;
	while(L){
		p=L;
		L=L->next;
		free(p); 
	}
	return OK;
}
单链表的打印:
Status ListTraverse(LinkList L)
{
    LinkList p=L->next;
    while(p)
    {
        printf("%d ",p->data);
        p=p->next;
    }
    printf("\n");
    return OK;
}
单链表的逆转:(有头结点)
void ReverseList(LinkList &L)
{
    LinkList old_head, new_head, Temp;
    new_head = NULL;
    old_head = L->next;

    while ( old_head )  {
        Temp = old_head->next;
        old_head->next = new_head;
        new_head = old_head;  
        old_head = Temp; 
    }
    
	L->next = new_head;
}
代码测试:
int main(){
	LinkList L;
    ElemType e;
    Status i;
    int j,k;
    i=InitList(L);
    printf("初始化L后:ListLength(L)=%d\n",ListLength(L));
    for(j=1;j<=5;j++)
            i=ListInsert(L,1,j);
    printf("在L的表头依次插入1~5后:L.data=");
    ListTraverse(L); 
    
	printf("ListLength(L)=%d \n",ListLength(L));
    i=ListEmpty(L);
    printf("L是否空:i=%d(1:是 0:否)\n",i);
    
    i=ClearList(L);
    printf("清空L后:ListLength(L)=%d\n",ListLength(L));
    i=ListEmpty(L);
    printf("L是否空:i=%d(1:是 0:否)\n",i);
    
    for(j=1;j<=10;j++)
            ListInsert(L,j,j);
    printf("在L的表尾依次插入1~10后:L.data=");
    ListTraverse(L); 
    
    printf("ListLength(L)=%d \n",ListLength(L));

	ListInsert(L,1,0);
    printf("在L的表头插入0后:L.data=");
    ListTraverse(L); 
    printf("ListLength(L)=%d \n",ListLength(L));
    
    GetElem(L,5,e);
    printf("第5个元素的值为:%d\n",e);
    
    for(j=3;j<=4;j++)
    {
            k=LocateElem(L,j);
            if(k)
                    printf("第%d个元素的值为%d\n",k,j);
            else
                    printf("没有值为%d的元素\n",j);
    }
    
    k=ListLength(L); /* k为表长 */
    printf("%d \n",k); 

	for(j=k+1;j>=k;j--)
    {
            i=ListDelete(L,j,e); /* 删除第j个数据 */
            if(i==ERROR)
                    printf("删除第%d个数据失败\n",j);
            else
                    printf("删除第%d个的元素值为:%d\n",j,e);
    }
    printf("依次输出L的元素:");
    ListTraverse(L); 
    
    i=ListDelete(L,5,e); /* 删除第j个数据 */
    if(i==ERROR)
        printf("删除第%d个数据失败\n",j);
    else
        printf("删除第%d个的元素值为:%d\n",j,e);
        

    printf("依次输出L的元素:");
    ListTraverse(L); 
    
    i=ClearList(L);
    printf("\n清空L后:ListLength(L)=%d\n",ListLength(L));
    CreateListHead(L,20);
    printf("整体创建L的元素(头插法):");
    ListTraverse(L); 
    
    i=ClearList(L);
    printf("\n删除L后:ListLength(L)=%d\n",ListLength(L));
    CreateListTail(L,10);
    printf("整体创建L的元素(尾插法):");
    ListTraverse(L); 
    LinkList pCur = L->next;
    
    k=ListLength(L); /* k为表长 */
    printf("ListLength(L)=%d \n",k); 
    
    printf("L逆转: "); 
    ReverseList(L);
    ListTraverse(L); 
	return 0;
}

在这里插入图片描述


希望可以对大家有帮助,若发现错误请及时反馈。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

程序猿小张的日常笔记

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值