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

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

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

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

优点:

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

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

缺点:

  1. 插入和删除操作需要移动大量元素。(耗费时间)
  2. 当线性表长度变化较大时,难以确定存储空间的容量。

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

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

1. 单链表

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

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

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

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

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

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

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

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


理解(要点):

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

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

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

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

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

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

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

  • ​ 优点:随机访问元素效率高。
  • ​ 缺点: 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;

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

小编分析: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;
}

单链表的元素插入:

图解:

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

(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;
}

单链表的元素删除:

图解:

学习笔记:2.3 线性表的链式表示和实现(单链表详解)
将前继结点绕过指向后继结点即可。

算法思路:

(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;
}

单链表的逆转:(有头结点)

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;
}

(后续会陆续发布静态链表,循环链表,双向链表)
学习笔记:2.3 线性表的链式表示和实现(单链表详解)

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

上一篇:线性链表(线性表的单链表存储结构)


下一篇:二、C语言实现头结点的单链表