萌新七叶的数据结构初学笔记——线性表

与世无争的帅哥 提交于 2020-01-17 01:02:32

目录

一 线性表

二 顺序表

1.静态分配

2.动态分配

1.使用typedef有什么用处

2.使用typeof有什么好处

3.为什么使用ElemType作为数据类型

3.顺序表的基本操作

1.初始化

2.创建

3.取值

4.查找

5.插入

6.删除

三 单链表

1.单链表的存储方式

2.单链表的基本操作

1.初始化

2.创建

3.取值

4.查找

5.插入

6.删除

四 双向链表

五 循环链表

六 线性表的应用


一 线性表

线性表是由n(n>=0)个相同类型的数据元素组成的有限序列,它是最基本、最常用的一种线性结构。顾名思义,线性表就像一条线,不会分叉。线性表有唯一的开始和结束,除了第一个元素外,每个元素都有唯一的直接前驱:除了最后一个元素外,每个元素都有唯一的直接后继。

线性表有两种存储方式:顺序存储和链式存储。采用顺序存储的线性表称为顺序表,采用链式存储的线性表称为链表。链表又分为单链表、双向链表和循环链表。

二 顺序表

顺序表采用顺序存储方式,即逻辑上相邻的数据在计算机内的存储位置也是相邻的。顺序存储方式,元素存储是连续的,中间不允许有空,可以快速定位第几个元素,但是插入和删除时需要移动大量元素。根据分配空间方法不同,顺序表可以静态分配和动态分配两种方法。

1.静态分配

顺序表最简单的方法是使用一个定长数组data[ ]存储数据,最大空间为Maxsize,用length记录实际的元素个数,即顺序表的长度。这种用定长数组存储的方法称为静态分配。

  0 1 2 3 4 5 6 .... Maxsize - 1
data[ ] 1 2 5 3 6 8 4    
  实际 元素 个数 length = 7    

 

 

 

 

采用静态分配方法,定长数组需要预先分配一段固定大小的连续空间。但是在运算的过程中,如合并、插入等操作,容易超过预分配的空间长度,出现溢出。解决静态分配的溢出问题,可以采用动态分配的方法。

2.动态分配

在程序运行过程中,根据需要动态分配一段连续的空间(大小为Maxsize),用elem记录该空间的基地址(首地址),用length记录实际的元素个数,即顺序表的长度。

基地址   M a x s i z e  

elem ->

L e n g t h      
  a1 a2 ... ai ... an      

 

 

 

 

采用动态存储方法,在运算过程中,如果发生溢出,可以另外开辟一块更大的存储空间,用以替换原来的存储空间,从而达到扩充存储空间的目的。

结构体定义的解释说明如下:

1.使用typedef有什么用处

typeof是C/C++语言的关键字,用于给原有数据类型起一个别名,在程序中可以等价使用,语法规则如下:

type 类型名称 类型标识符;

“类型名称”为已知数据类型,包括基本数据类型(如int、float等)和用户自定义数据类型(如用struct自定义的结构体)。

“类型标志符”是为原有数据类型起的别名,需要满足标识符命名规则。

2.使用typeof有什么好处

1.简化比较复杂的类型说明

给复杂的结构体类型起一个别名,这样就可以使用这个别名等价该结构体类型,在声明该类型变量时就方便多了。

不使用typeof的顺序表定义:

struct SqList{
    int* elem;    //顺序表的基地址
    int  length;    //顺序表的长度
};

如果需要定义一个顺序表,需要写:

struct SqList L;    //定义时需要加上struct(c需要,c++不需要),L为顺序表的名字

使用typeof的顺序表定义:

typedef struct{
    int* elem;    //顺序表的基地址
    int  length;    //顺序表的长度
}SqList;

如果需要定义一个顺序表,需要写:

SqList L;    //不需要写struct,直接用别名定义

2.提高程序的可移植性

在程序中使用这样的语句:

typedef int ElemType;    //给int起个别名ElemType

在程序中,假如有n个地方用到了ElemType类型,如果现在处理的类型变为字符型了,那么就可以将上面类型定义中的int直接改为char。

typedef char ElemType;

这样只需要修改类型定义,不需要改动程序中的代码。如果不使用typedef类型定义,就需要把程序中n个用到int类型的地方全部改为char类型。如果某处忘记修改,就会产生错误。

3.为什么使用ElemType作为数据类型

使用ElemType是为了让算法的通用性更好,因为使用线性表的结构体定义后,并不清楚具体问题处理的数据是什么类型,不能简单地写成某一类型,不能简单地写成某一种类型。结合typedef使用,可以提高算法的通用性和可移植性。

以int型元素为例,如果使用顺序表的动态分配结构体定义,就可以直接将ElemType写成int。

typedef struct{
    int *elem;    //顺序表的基地址
    int  length;    //顺序表的长度
}SqList;

也可以使用类型定义,给int起个别名:

typedef int struct;    //给int起个别名ElemType,两者等价
typedef struct{
    ElemType* elem;    //顺序表的基地址
    int length;    //顺序表的长度
}SqList;

显然,后一种定义的通用性和可移植性更好,当然第一种定义也没有错。

3.顺序表的基本操作

1.初始化

初始化是指为顺序表分配一段预定义大小的连续空间,用elem记录这段空间的基地址,当前空间内没有任何数据元素,因此元素的实际个数为零。假设我们已经预定义了一个最大空间数Maxsize,那么就用new分配大小为Maxsize的空间,分配成功会返回空间的首地址,分配失败会返回空指针。

bool InitList(SqList &L)    //构造一个空的顺序表L
{    //L前面加&表示引用参数,函数内部的改变挑出函数后仍然有效
     //如果不加&,函数内部的改变在跳出函数后便会无效
    L.elem = new int {Maxsize};    //为顺序表动态分配,axsize个空间
    if(!l.elem)
        return false;    //分配空间失败
    L.length = ;    //顺序表长度为0
    return ture;
}

初始化后的顺序表

L.elem 0 1 2 3 4 5 6 ... Maxsize - 1
                   
L.length = 0                  

 

 

 

 

2.创建

顺序表创建是向顺序表中输入数据,输入数据的类型必须与类型定义中的类型一致

算法步骤

  1. 初始化下标变量 i = 0,判断顺序表是否已满,如果是则结束;否则执行第二步。
  2. 输入一个变量元素 x。
  3. 将数据 x 存入顺序表的第 i 个位置,即L.elem[i] = x,然后i++。
  4. 顺序表长度加1,即L.length++。
  5. 直到数据输入完毕。

图解

1.输入元素:5。将数据元素5存入顺序表的第0个位置,即L.elem[0] = 5,然后i++。

L.elem-> 0 1 2 3 4 5 6 ... Maxsize - 1
  5                
L.length = 1   i              

 

 

 

 

2.输入元素:3。将数据元素3存入顺序表的第1个位置,即L.elem[1] = 3,然后i++。

L.elem-> 0 1 2 3 4 5 6 ... Maxsize - 1
  5 3              
L.length = 2     i            

 

 

 

 

3.输入元素:9。将数据元素9存入顺序表的第2个位置,即L.elem[2] = 9,然后i++。

L.elem-> 0 1 2 3 4 5 6 ... Maxsize - 1
  5 3 9            
L.length = 3       i          

 

 

 

 

代码实现

bool CreateList(SqLisr &L)    //创建一个顺序表L
{    //L加&表示引用类型参数,函数内部的改变跳出函数仍然有效
     //不加&则在内部改变时,跳出函数后无效
    int x,i = 0;
    while(x != -1)    //输入-1时结束,也可以设置其他的结束条件
    {
        if(L.length == Maxsize)
        {
            cout << "顺序表已满!";
            return flase;
        }
        cin >> x;    //输入一个元素
        L.elem[i++] = x;    //将数据存入第i个位置,然后i++
        L.length++;    //顺序表长度加1
    }
    return ture;
}

3.取值

顺序表中的任何一个元素都可以立即找到,称为随机存取方式。

例如,要取第i个元素时,只要i值是合法的(1<= i <= L.length),那么立即可以找到该元素。由于下标是从0开始的,因此第i个元素,其下标为i - 1,即对应元素为L.elem[i - 1]。

L.eken-> 0 1 2 ... i - 1   ...   Maxsize - 1
  a1 a2 a3 ... ai ... ... an  
          i        

 

 

 

 

注意:位序是指第几个元素,位序和下标差1。

代码实现:

bool GetElen(SqList L, int i, int &e)
{
    if(i < 1 || i > L.length)    return false;
    //判断i值是否合理,若不合理,则返回flase
    e = L.elem[i - 1];    //第i - 1个单元存储着第i个数据
    return ture;
}

4.查找

在顺序表中查找一个元素e,可以从第一个元素开始顺序查找,依次比较每一次元素值。如果相等,则返回-1.

代码实现:

int LocateElem(Sqlist L. int e)
{
    for (i = 0; i < L.length; i++)
        if (L.elem[i] == e)    return i + 1;    //下标为i,实际为i+1个元素
    return -1;    //如果没找到,则返回-1
}

5.插入

在顺序表中第i个位置之前插入一个元素e,需要从最后一个元素开始,后移一位……直到把第i个元素也后移一位,然后把e放入第i个位置。

基地址L.elem->         n - i + 1个   后移一位    
  a1 a2 ... ai -> ... -> an ->      

 

 

 

算法步骤

  1. 判断插入位置i是否合法(1 <= i <= L.length +1 ),可以在第一个元素之前插入,也可以在第L.length + 1个元素之前插入。
  2. 判断顺序表的存储空间是否已满。
  3. 将第L.length至第i个元素依次向后移动一个位置,空出第i个位置。
  4. 将要插入的新元素e放入第i个位置。
  5. 表长加1,插入成功返回ture。

完美图解:

例:在顺序表中的第5个位置之前插入一个元素9。

 

          位 ->  
L.elem -> 0 1 2 3 4 5 6 7 Maxsize - 1
  3 5 6 7 2 8 10 1  
L.length = 8                  

 

 

 

 

 

插入过程如下:

1.移动元素。从最后一个元素(下标为L.length - 1)开始后移一位。

L.elem -> 0 1 2 3 4 5 6 7   Maxsize - 1
  3 5 6 7 2 8 10 -> 1  
L.length = 8                    

 

 

 

L.elem -> 0 1 2 3 4 5 6 7   Maxsize - 1
  3 5 6 7 2 8 -> 10 1  
L.length = 8                    

 

 

 

 

L.elem -> 0 1 2 3 4 5 6 7   Maxsize - 1
  3 5 6 7 2 -> 8 10 1  
L.length = 8                  

 

 

 

 

L.elem -> 0 1 2 3 4 5 6 7   Maxsize - 1
  3 5 6 7 -> 2 8 10 1  
L.length = 8                  

 

 

 

 

 

2.插入元素。此时第5个位置空出来,将要插入的新元素9放入第5个位置,表长加1。

L.elem -> 0 1 2 3 4 5 6 7   Maxsize - 1
  3 5 6 7 9 2 8 10 1  
L.length = 8                  

 

 

 

 

 

代码实现:

bool ListInsert_Sq(SqList &L, int i, int e)
{
    if(i < 1 || i > L.length + 1)    return false;    //i值不合法
    if(L.length == Maxsize)    return false;    //存储空间已满
    for(int j = L.length - 1; j >= i - 1; j--)
        L.elem[j + 1] = L.elem[j];    //从最后一个元素开始后移,直到第i个元素后移
    l.elem[i - 1] = e;    //将新元素e放入第i个位置
    L.length++;    //表长加1
    return ture;
}

6.删除

在顺序表中删除第i个元素,需要把该元素暂存到变量e中,然后从i + 1个元素开始前移……直到把第n个元素也前移一位,即可完成删除操作。

基地址L.elem ->       删除元素   n - i个 前移一位
  a1 a2 ...     ai <- ai+1 <-... <- an

 

 

 

算法步骤:

  1. 判断删除位置i是否合法(1 <= i <= L.length),
  2. 将欲删除的元素保存在e中。
  3. 将第i + 1至第n个元素依次向前移动一个位置。
  4. 表长减1,删除成功,返回ture。

图解:

从顺序表中删除第5个元素。

          删除元素 <- 前移一位      
L.enem -> 0 1 2 3      4 5       6 7   Maxsize - 1
L.length = 8 3 5 6 7      2 8      10 1    

 

 

 

 

删除过程如下:

1.移动元素。首先将待删除元素2暂存到变量e中,以后可能有用,如果不暂存,将会被覆盖,然后从第6个元素开始前移一位。

L.enem -> 0 1 2 3  4 5  6 7   Maxsize - 1
L.length = 8 3 5 6 7 8 <- 10 1    

 

 

 

L.enem -> 0 1 2 3  4 5  6 7   Maxsize - 1
L.length = 8 3 5 6 7 8 10 <- 1    

 

 

 

L.enem -> 0 1 2 3  4 5  6 7   Maxsize - 1
L.length = 8 3 5 6 7 8 10 <- 1    

 

 

 

2.表长减1。

L.enem -> 0 1 2 3  4 5  6 7   Maxsize - 1
L.length = 8 3 5 6 7 8 10 1      

 

 

 

代码实现:

bool ListDelete_Sq(SqList &L, int i, int &e)
{
    if(i < 1 || i >L.Length)    return false;    //i值不合法
    e = L.elem[i - 1];    //将欲删除的元素保存在e中
    for (int j = 1;j <= L.length - 1; j++)
        L.elem[j - 1] = L.elem[j];    //被删除元素之后的元素前移
    L.length--;    //表长减1
    return true;
}

顺序表的优点:

操作简单,存储密度高,可以随机存取,只需要O(1)的时间就可以取出第i个元素。

顺序表的缺点:

需要预先分配最大空间,最大空间数估计过大或过小会造成空间浪费或溢出。插入和删除操作需要移动大量元素。

三 单链表

链表是线性表的链式存储方式。逻辑上相邻的数据在计算机内的存储位置不一定相邻。

1.单链表的存储方式

可以给每个元素附加一个指针域,指向下一个元素的存储位置。

  数据元素 下一个元素的地址        
-> ai   -> ai+1   ->

 

 

 

每个节点包括两个域:数据域和指针域。数据域存储数据元素,指针域存储下一个节点的地址,因此指针指向的类型也是节点类型。每个指针都指向下一个节点,都是朝一个方向的,这样的链表称为单向链表或单链表

定义了节点结构体之后,就可以把若干个节点连接在一起,形成一个单链表。

                          NULL
a1   -> a2   -> ... ai   -> ... -> an ^

 

 

 

只要给这个单链表设置一个头指针,这个链表中的每个节点就都可以找到了。

头指针L                         NULL
a1   -> a2   -> ... ai   -> ... -> an ^

 

 

 

有时为了操作方便,还会给链表增加一个不存放数据的头节点(也可以存放表长等信息)。

头指针L 头节点                             NULL
      a1   -> a2   -> ... ai   -> ... -> an ^

 

 

 

在顺序表中,想找第i个元素,可以立即通过L.elem[i -1]找到,想找哪个找哪个,称为随机存取。但是在单链表中,想找第i个元素就没那么容易,必须从头开始,按顺序一个一个找,一直数到第i个元素,称为顺序存取

2.单链表的基本操作

下面以带头节点的单链表为例,讲解单链表的初始化、创建、取值、查找、插入、删除等基本操作。

1.初始化

单链表的初始化是指构建一个空表。先创建一个头节点,不存储数据,然后令其指针域为空。

头指针L NULL
  ^

 

 

 

代码实现:

bool InitList_L(LinkList &L)
{
    L = new LNode;    //生成新节点作为头节点,用头指针L指向头节点
    if(!L)    return false;    //生成节点失败
    L->next = NULL;    //头节点的指针域置空
    return ture;
}

2.创建

创建单列表分为头插法尾插法两种,头插法是指每次把新节点插到头节点之后,其创建的单链表和数据输入顺序正好相反,因此也称为逆序建表。尾插法是指每次把新节点链接到链表的尾部,其创建的单链表和数据输入顺序一致,因此也称为正序链表。

我们先讲头插法建表,头插法每次把新节点插入到头节点之后,创建的单链表和数据输入顺序相反。

图解:

1.初始状态。初始状态是指初始化后的空表,只有一个头节点。

2.输入数据元素1,创建新节点,把元素1放入新节点数据域。

头指针L NULL
  ^
s  
1

->

s = new LNode;
cin >> s->data;

3.头插操作,插入头-节点的后面。

头指针L    
  ^ ->[2]
s  
1

^[1]

s->next = L->next;    //[1]
L->next = s;    //[2]

 

4.输入数据元素2,创建新节点,把元素2放入新节点数值域。

s  
2

->

 

 

 

5.头插操作,插入头节点的后面。

头指针L  
  ^[2]
s  
2

->[1]

s  
1

->

s->next = L->next;    //[1]
L->next = s;    //[2]

 

赋值解释

假设赋值之前节点的地址计指针为

头指针L  
  9630
  9630  
-> 1 ^
2046 s
2  

赋值语句两端,等号的右侧是节点的位置,等号的左端是节点的指针域。

1.s->next = L->next:L->next存储的是下一个节点地址“9630”,将该地址赋值给s->next指针域,即s节点的next指针指向1节点。

头指针L  
  9630
  9630  
-> 1 ^
  2046 s
<-[1] 2 9630

2.L->next = s:将s节点的地址“2046”赋值给L->next指针域,即L节点的next指针指向s节点。

头指针L  
  9630
  2046 s
->[2] 2 9630
  9630  
->[1] 1 ^

 

 

 

修改指针顺序

为什么要修改后面的那个指针?

因为一旦要修改了L节点的指针域指向s,那么原来的L节点就找不到了,因此修改指针是有顺序的。

修改指针的顺序原则:先修改没有指针标记的那一端。

如果要插入节点的两端都有标记。例如,再定义一个指针q指向L节点后面的节点,那么先修改哪个指针都无所谓了。

6.拉直链表。

头指针L    
    ->
   
2

->

   
1

^

 

 

 

7.继续依次输入数据元素3、4、5、6、7、8、9、10。

头插法创建的单链表(逆序):

头指针L                              
    -> 10   -> 9   -> ... -> 2   -> 1  

 

 

 

可以看出,头插法创建的单链表与数据输入顺序正好相反。

代码实现:

void CreateList_H(LinkList &L)    //头插法创建单链表
{
    int n;    //输入n个元素的值,建立到头节点的单链表L
    LinkList  s;    //定义一个指针变量
    L = new LNode;
    L->next = NULL;    //先建立一个带头节点的空链表
    cout << "请输入元素个数n:" << endl;
    cout << "头插法创建单链表……" << endl;
    while(n--)
    {
        s = new LNode;    //生成新节点s
        cin >> s->data;    //输入元素值赋值给新节点的数值域
        s->next = L->next;
        L->next = s;    //将新节点s插入头节点之后
    }
}

尾插法每次把新节点链接到链表的尾部,因此需要一个尾指针永远指向链表的尾结点。

1.初始状态。初始状态是指初始化的空表,只有一个头节点,设置一个尾指针r指向该节点。

头指针L 尾指针r
  ^

 

 

 

2.输入数据元素1,创建新节点,把元素1放入新节点数据域。

s = new LNode;    //生成新节点s
cin >> s->data;    //输入数据元素赋值给新节点的数据域
s  
1

->

 

 

 

3.完成尾插操作,插入尾结点的后面。

头指针L r
  ^
  s r[3]
->[2] 1

 ->[1]

 

 

 

赋值解释:

s->next = NULL;    //【1】:s节点的指针域置空
r->next = s;    //【2】:将s节点的地址赋值给r节点的指针域,即将新节点s插入尾结点r之后
r = s;    //【3】:将s节点的地址赋值给r,即r指向新的尾结点s。

4.输入数据元素2,创建新节点,把元素2放入新节点数据域。

s  
2

->

 

 

 

5.完成尾插操作,插入尾结点的后面。

头指针L r
  ^
  r    
-> 1  

 ->[2]

s r[3]
2  

 

 

 

6.继续依次输入数据元素3、4、5、6、7、8、9、10。

头指针L  
   
       
-> 1  

 ->

     
2  

->

   
3  
               
-> ... -> 9   -> 10  

 

 

 

代码实现:

void CreateList_R(LinkList &L)    //尾插法创建单链表
{
    //输入n个元素的之,建立代表头节点的单链表L
    int n;
    LinkList s, t;
    L = new LNode;
    L->next = NULL;    //先建立一个带头结点的空链表
    r = L;    //尾指针r指向头节点
    cout << "请依次输入n个元素:" << endl;
    cin >> n;
    cout << "尾插法创建单链表……" << endl;
    while(n--)
    {
        s = new LNode;    //生成新节点
        cin >> s->data;    //输入元素值赋给新节点的数值域
        s->next = NULL;
        r->next = s;    //将新节点s插入尾结点之后
        r = s;    //指向新的尾结点s
    }
}

3.取值

单链表的取值不像顺序表那样可以随机访问任何一个元素,单链表只有头指针,各个节点的物理地址是不连续的。要想找到第i个节点,就必须从第一个节点开始按照顺序向后找,一直找到第i个节点。

注意:链表的头指针不可以随意改动!

一个链表是由头指针来标识的,一旦头指针改动或丢失,这个链表就不完整或找不到了。所以链表的头指针是不能随意改动的,如果需要用指针移动,可定义一个指针变量进行移动。

算法步骤:

  1. 先定义一个p指针,指向第一个元素节点,用j作为计数器,j = 1。
  2. 如果p不为空且j < 1,则p指向p的下一个节点,然后j加1,即:p = p->necxt; j++。
  3. 直到p为空或者j = i时停止。p为空,说明没有数到i,链表就结束了,即不存在第i个节点:j = i,说明找到了第i个节点。

完美图解:

1.p指针指向第一个元素节点,j = 1。

头指针L     p j = 1                
    -> a1   -> a2   -> ... -> an ^

 

 

 

2.p指针指向第二个元素节点,j = 2.

头指针L           p j = 2          
    -> a1   -> a2   -> ... -> an ^

 

 

 

3.p指针指向第i个节点,j = i。

头指针L               p j = 1      
    -> a1   -> ... -> ai   -> an ^

 

 

 

代码实现:

bool GetElem_L(LinkList L, int i, int &e)
{
    //在带头节点的单链表L中查找第i个元素
    //用e记录L中第i个数据元素的值
    int j;
    LinkList p;
    p = L->next;    //p指向第一个数据节点
    j = 1;    //j为计数器
    while(j < i && p)    //顺着链表向后扫描,直到p指向第i个元素或p为空
    {
        p = p->next;    //p指向下一个节点
        j++;    //计数器
    }    
    if(!p || j > i)    //i值不合法
        return false;
    e = p->data;    //取第i个节点的数据域
    return true;
}    

4.查找

在一个单链表中查找是否存在元素e,可以定义一个p指针,指向第一个元素节点,比较p指向节点的数据域是否等于e。如果相等,查找成功,返回true;如果不等,则p指向下一个节点,继续比较,如果p为空,查找失败,返回false。

头指针L     p         p        
    -> a1   -> ... -> ai   -> an ^

 

 

 

代码实现:

bool LocaeElem_L(LinkList L, int e)    //在带头节点的单链表L中查找值为e的元素
{    
    LinkList p;
    p = L->next;
    while(p && p->data != e)    //沿着链表向后扫描,直到p为空或p所指节点数据域等于e
        p = p->next;    //p指向下一个节点
    if(!p)    return false;    //查找失败,p为NULL
} 

5.插入

如果要在第i个节点之前插入一个元素,则必须先找到第i - 1个节点。

单链表只有一个指针域,是向后操作的,不可以向前操作。如果直接找到第i个节点,就无法向前操作,把新节点插入第i个节点之前。实际上,在第i个节点之前插入一个元素相当于在第i - 1个节点之后插入一个元素,因此先找到第i - 1个节点,然后将新节点插在其后面即可。

算法步骤:

  1. 定义一个p指针,指向头节点,用j作为计数器,j = 0。
  2. 如果p不为空且j < i - 1,则p指向p的下一个节点,然后j + 1,即:p = p->next;j++。
  3. 直到p为空或j >= i - 1停止。
  4. p为空,说明没有数列i - 1,链表就结束了,即i > n + 1,i值不合法;j > i - 1说明i < 1,此时i值不合法,返回false。如果j = i - 1说明找到了第i - 1个节点。
  5. 将新节点插到第i - 1个节点之后。

图解:

假设已经找到了第i - 1个节点,并用p指针指向该节点,s指向待插入的新节点,则插入操作为:

  p    
-> ai-1   ->[2]
s    
e   ->[1]
  s    
- -> e   ->

 

 

 

赋值解释:

  1. s->next = p->next:将p节点后面的节点地址赋值给s节点的指针域,即s节点的next指针指向p后面的节点。
  2. p->next = s:将s节点的地址赋值给p节点的指针域,即p节点的next指针指向s节点。

前插法建链表,就是将新节点插到头节点之后,现在是将新节点插到第i - 1个节点之后。

bool ListInsert_L(LinkList &L, int i, int e)
{
    //在带头节点的单链表L中第i个位置之前插入值为e的新节点
    int j;
    LinkList p, s;
    p = L;
    j = 0;
    while(p && j < i - 1)
    {
        p = p->next;
        j++;
    }
    if(!p || j > i - 1)    //i > n + 1或者i < 1
        return false;
    s = new LNode;    //生成新节点
    s->next = p->next;    //将新节点的指针域指向第i个节点
    p->next = s;    //将节点p的指针域指向节点s
    return ture;
}

6.删除

删除一个节点,实际上是把这个节点跳过去。根据单向链表向后操作的特性,要想跳过第i个节点,就必须先找到第i - 1个节点,否则是无法跳过去的。

 

p

    q          
-> ai - 1   ->

ai

  -> ai + 1   ->

 

|____ ___ ___ ___ ___ ____ ___|    

 

 

 

 

赋值解释:

p->next = q->next的含义是将q节点的下一个节点地址赋值给p节点的指针域。等号的右侧是节点的地址,等号的左侧是节点的指针域。

  0985     p     2046   q     1013    
-> ai - 1 2046 - - - > ai 1013 --> ai + 1   -->
  |___________ _______ _____ _______ ____ ___ ____|    
  节点的指针域 p->next   =     q->next 节点的位置  

 

 

 

 

 

假设q节点的下一个节点地址是1013,该地址存储在q0>next里面,因此等号右侧的q->next的值为1013。把该地址赋值给p节点的next指针域,把原来的值2046覆盖掉,这样p->next也为1013,相当于把q节点跳过去了。赋值之后,用delete q释放被删除节点的空间

  0985     p     2046   q     1013    
-> ai - 1 1013 - - - > ai 1013 --> ai + 1   -->
  |___________ _______ _____ _______ ____ ___ ____|    

 

 

 

 

代码实现:

bool ListDelete_L(LinkList &L, int i)    //单链表的删除
{
    //在带头节点的单链表L中,删除第i个位置
    LinkList p, q;
    int j;
    p = L;
    j = 0;
    while((p->next) && (j < i - 1))    //查找第i - 1个节点,p指向该节点
    {
        p = p->next;
        j++;
    }
    if(!(p->next) || (j > i - 1))    //当i > n或i < 1时,删除位置不合理
        return false;
    q = p->next;    //临时保存被删节点的地址以备释放空间
    p->next = q->next;    //将q节点的下一个节点地址赋值给p节点的指针域
    delete q;    //释放被删除节点的空间
    return true;
}

在单链表中,每个节点除存储自身数据之外,还存储了下一个节点的地址,因此可以轻松的访问下一个节点,以及后面的所有后继节点。但是,如果想访问前面的节点就不行了,再也回不去了。

例如,删除节点q时,要先找到它的前一个节点p,然后才能删掉q节点,单向链表只能向后操作,不可以像前操作。

四 双向链表

鸽了

五 循环链表

鸽了

六 线性表的应用

鸽了


参考文献:

1.趣学数据结构.陈小玉.2019

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!