IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 数据结构第二章——线性表的链式表示 -> 正文阅读

[数据结构与算法]数据结构第二章——线性表的链式表示

目录

一、单链表的定义

(一)头指针?

(二)头结点

二、单链表基本操作的实现

(一)采用头插法建立单链表

(二)采用尾插法建立单链表

(三)按序号查找节点值

(四)按值查找节点

(五)插入结点操作

(六)删除节点操作

(七)求表长操作

三、双链表

(一)双链表的定义

(二)双链表的后插操作

(三)双链表的删除操作

四、循环链表

(一)循环单链表的定义

(二)循环双链表的定义

?五、静态链表

六、经典例题

(一)选择题

(二)应用题


一、单链表的定义

????????通过一组任意的存储单元来存储线性表中的数据元素

typedef struct LNode
{
    ElemType data;
    struct LNode *next;
}LNode,*LinkList;

????????对上述代码进行简单说明,LNode和*LinkList都是结构体LNode的别名,定义两个别名的目的是,LNode用来定义结点,而LinkList用来作为头指针的定义?ElemType是一个抽象概念,就是代表任意数据类型的意思,在实际操作中不存在这个类型,只是描述算法中使用,具体场景中使用int、char等真实存在的数据类型进行替换即可

(一)头指针?

????????通常用头指针来标识一个单链表,如单链表L,头指针为NULL时标识一个空表

? ? ? ? 不论有无头结点,头指针都始终指向链表的第一个结点

(二)头结点

? ? ? ? 头结点的数据域可以不设任何信息,也可以记录表长等信息

  • 链表的第一个位置上的操作和其他位置的操作一致,无需特殊处理
  • 无论链表是否为空,其头指针都指向头结点的非空指针,因此空表和非空表的处理得到了统一

? ? ? ? 判断是否带头结点,就看有没有给头指针申请分配内存


二、单链表基本操作的实现

(一)采用头插法建立单链表

1.带头结点的头插法算法思路:

? ? ? ? ①先申请一个头指针并将其指向空地址

????????②将头指针指向头结点

? ? ? ? 也就是给头指针分配内存,此时头指针的值就是malloc分配的内存空间的地址值,也就是头指针指向头结点

????????③创建新的节点,将读取到的数据存放到新节点的数据域中

????????再申请一个指针,给指针分配内存就算完成创建了

????????④将新节点插入到头结点后面

? ? ? ? 先头结点指向新节点,再把新节点指向? ?原本头结点指向的位置

????????⑤然后重复第③、④步以此循环

typedef struct LNode
{
    ElemType data;
    struct LNode *next;
}LNode,*LinkList;


void List_HeadInsert(LinkList &L)
{
    LNode *s;
    int x;
    L = (LinkList)malloc(sizeof(LNode));        //创建头结点
    L->next = NULL;                             //初始为空链表
    scanf("%d",&X);                             //输入节点的值
    while(x != 9999)                            //输入9999表示结束
    {
        s = (LNode*)malloc(sizeof(LNode));      //创建新节点
        s->data = x;
        s->next = L->next;
        L->next = s;                            //新节点插入表中,L为头指针
        scanf("%d",&x);
    }
    return L;
}


int main()
{
    LinkList L = NULL;                          //空链表初始化
    List_HeadInsert(&L);
    return 0;
}

2.不带头结点的头插法算法思路:?

?????????①先申请一个头指针并将其指向空地址

? ? ? ? ②创建一个新节点并为其赋值? ?

????????再申请一个指针,给指针分配内存就算完成创建了

? ? ? ? ③将新节点插入到头指针后面

????????判断头指针是否为空指针,如果是,把头指针指向新节点;

? ? ? ? ? ?????????????????????????????????????????若不是,则把新建节点指向原本头指针指向的节点,头指针指向? ? ? ? ? ? 新建节点

????????④重复第②、③步以此循环

typedef struct LNode
{
    ElemType data;
    struct LNode *next;
}LNode,*LinkList;


void NoNode_HeadInsert(LinkList &L)
{
    //初始化链表
    LNode *s;                                      //要插入的节点
    ElementType x;                                 //要插入的数据
    scanf("%d", &x);
    while (x != 999) 
    {
        s = (LNode*)malloc(sizeof(LNode));
        s->data = x;
        s->next = NULL;
        if (NULL == L) 
        {
            L = s;                                //将新结点置位首节点也是尾结点
        } 
        else 
        {
            s->next = L;
            L = s;                                //新结点置位尾结点
        }
        scanf("%d", &x);
    }

}


int main()
{
    LinkList L = NULL;                             //空链表初始化
    NoNode_HeadInsert(&L);
    return 0;
}

(二)采用尾插法建立单链表

1.带头结点的尾插法算法思路:

? ? ? ? ①先申请一个头指针并将其指向空地址

????????②将头指针指向头结点

? ? ? ? 也就是给头指针分配内存,此时头指针的值就是malloc分配的内存空间的地址值,也就是头指针指向头结点

????????③创建一个表尾指针

????????意思就是表尾指针初始化指向头结点,第一次使用时可充当头指针的作用

? ? ? ? ④新建一个结点并为其赋值

????????再申请一个指针,给指针分配内存就算完成创建了

????????⑤将新节点挂在表尾

? ? ? ? 用表尾指针将表尾结点指向新节点,然后再将表尾指针指向新建节点即可

? ? ? ? ⑥重复④、⑤,以此循环?

typedef struct LNode
{
    ElemType data;
    struct LNode *next;
}LNode,*LinkList;


void List_TailInsert(LinkList &L)
{
    int x;
    L = (LinkList)malloc(sizeof(LNode));
    LNode *s,*r = L;
    scanf("%d",&x);
    while(x != 9999)
    {
        s = (LNode *)malloc(sizeof(LNode));         //创建新节点
        s->data = x;
        r->next = s;
        r = s;
        scanf("%d",&x);
    }
    r->next = NULL;
}


int main()
{
    LinkList L = NULL;                          //空链表初始化
    List_TailInsert(&L);
    return 0;
}

? ? ? ? 上述代码可能有个容易被误导的地方,?s = (LNode *)malloc(sizeof(LNode)),明明前面定义时LNode *s,*r = L将头结点的地址赋给s了,而且头结点也已经完成了内存分配创建,s与L值一样的怎么能再次分配内存。malloc函数是随机分配一块内存并且把该内存的地址赋给指针s,而不是给s原本的地址赋值,也就是说这里指针定义的时候可以不用LNode *s = L,直接LNode *s也没关系,当然LNode *r = L这一步是不能不赋值的

2.不带头结点的尾插法算法思路:

? ? ? ? ①先申请一个头指针并将其指向空地址

????????②创建一个表尾指针

????????意思就是表尾指针初始化指向头结点,第一次使用时可充当头指针的作用

? ? ? ? ④新建一个结点并为其赋值

????????再申请一个指针,给指针分配内存就算完成创建了

????????⑤将新节点挂在表尾

? ? ? ? 用表尾指针将表尾结点指向新节点,然后再将表尾指针指向新建节点即可

? ? ? ? ⑥重复④、⑤,以此循环?

typedef struct LNode
{
    ElemType data;
    struct LNode *next;
}LNode,*LinkList;


void NoNode_TailInsert(LinkList *L) {
    ElementType x;                                //要插入的元素
    LNode *s;                                     //要插入的节点
    LNode *r;                                     //尾指针
    scanf("%d", &x);
    while (x != 9999) {
        s = (LNode *) malloc (sizeof(LNode));
        s->data = x;
        s->next = NULL;
        if (NULL == L)                            //链表是否为空  
        {                                          
            L = s;                                //将新结点置位首节点也是尾结点
            r = L;
        } 
        else 
        {
            r->next = s;
            r = s;                                //新结点置位尾结点
        }
        scanf("%d", &x);
    }
}

int main()
{
    LinkList L = NULL;                          //空链表初始化
    NoNode_TailInsert(&L);
    return 0;
}

(三)按序号查找节点值

  • 先判断序号是否合法

? ? ? ? 序号为0,直接传出头结点,为小于0的数则返回NULL

  • 再往下进行循环

? ? ? ? 关键就在哪里停下来,满足节点不为空并且序号等于输入值的时候

typedef struct LNode
{
    ElemType data;
    struct LNode *next;
}LNode,*LinkList;

LNode *GetElem(LinkList L,int i)
{
    int j = 1;
    LNode *p=L->next;                           //头结点赋给p
    if(i == 0)
        return L;
    if(i<1)
        return NULL                             //若i无效,则返回NULL
    while(p&&j < i)
    {
        p=p->next;
        j++;
    }
    return p;
}

int main()
{
    LinkList L = NULL;                          //空链表初始化
    LNode *x;
    x = GetElem(L,5);
    return 0;
}

?? ? ? ? 查找的时候,带头结点的,头结点为第0个节点,第一个带数据的节点才是第1个结点

(四)按值查找节点

????????循环对比值即可?

? ? ? ? 循环条件是不为空并且值不等于输入的值

typedef struct LNode
{
    ElemType data;
    struct LNode *next;
}LNode,*LinkList;

LNode *LocateElem(LinkList L,ElemType e)
{
    LNode *p = L->next;
    while(p!=NULL&&p->data!=e)                  //循环条件是不为空以及值不等于要找的
        p = p->next;
    return p;
}

int main()
{
    LinkList L = NULL;                          //空链表初始化
    LNode *x;
    x = LocateElem(L,e);
    return 0;
}

(五)插入结点操作

1.后插节点

  • 先将新节点通过第一个结点指向下第二个节点
  • 再将第一个结点指向新结点
p = GetLem(L,i-1);
s->next = p->next;
p->next = s;

? ? ? ? 顺序不能颠倒,如果颠倒了,就找不到第二个节点在哪了?

2.前插节点

? ? ? ? 普通的前插节点要从头找该节点的前一个结点,插入时间复杂度为O(n),因此使用后插法再加上交换数据域方式来实现,时间复杂度为O(1)

s->next = p->next;
p->next = s;
temp = p->data;
p->data = s->data;
s->data = temp;

(六)删除节点操作

1.找前驱删除法?

????????①删除节点

? ? ? ? 先查找要删除位置的前驱节点,然后把前驱节点直接指向要删节点的后继节点

????????②释放删除节点空间

? ? ? ? 使用free函数

p = GetElem(L,i-1);
q = p->next;
p->next = q->next;
free(q);

2.后驱交换删除法?

? ? ? ? ①找到要删除的节点位置

? ? ? ? ②要删节点与其后驱节点交换数据域

? ? ? ? ③删除后驱节点即可

q = p->next;
p->data = p->next->data;
p->next = q->next;
free(q);

(七)求表长操作

?? ? ? ? 先从头指针定位到头结点,然后一直循环往后查找到为空

? ? ? ? 链表长度不包括头结点


三、双链表

(一)双链表的定义

?????????产生原因,单链表在某个节点只能找到后继节点,找前驱节点必须从头开始找,因此诞生了部分后驱节点交换数据域的算法。直接再设置一个前驱节点就完事了,这就是双链表

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

(二)双链表的后插操作

1.算法思路??

2.代码实现?

s->next = p->next;
p->next-prior = s;
s->prior = p;
p->next = s;

(三)双链表的删除操作

1.算法思路?

2.代码实现?

p->next = q->next;
q->next->prior = p;
free(q);


四、循环链表

(一)循环单链表的定义

? ? ? ? 与单链表的区别在于,表中最后一个节点的指针不是NULL,而改为指向头节点,从而整个链表形成一个环

? ? ? ? 循环链表的判空条件不是头节点的指针是否为空,而是他是否等于头指针?

?

(二)循环双链表的定义

? ? ? ? 循环双链表中,头节点的prior指针指向表尾节点

? ? ? ? 判断循环双链表节点为空的条件,头节点的prior和next域都等于头指针L?


?五、静态链表

? ? ? ? ?静态链表借助数组来描述线性表的链式存储,指针域是节点的相对地址,也就是数组的下标,称为游标

????????普通链表的指针里是绝对地址

? ? ? ? 和顺序表一样,静态链表也要预先分配一块连续的内存空间

? ? ? ? 指针指向-1的就是尾结点

#define MaxSize 50
typedef struct
{
    ElemType data;
    int next;
}SLinkList[MaxSize];

? ? ? ? ?总体来说,静态链表没有单链表使用方便,但在一些不支持指针的高级语言(如Basic)中,这是一种非常巧妙的设计方法


六、经典例题

(一)选择题

1.给定有n个元素的一维数组,建立一个有序单链表的最低时间复杂度是()

? ? ? ? A.O(1)? ? ? ? ? ? ? ?B.O(n)? ? ? ? ? ? ? ? C.O(n^2)? ? ? ? ? ? ? ? D.O(nlog_2n)

? ? ? ? 如果先建立链表,然后依次插入建立有序表,每插一个元素就需遍历链表寻找插入位置,时间复杂度为O(n^2)

? ? ? ? 如果先将数组排好序,再建立链表。建立链表的时间复杂度为O(n),数组排序最好时间复杂度为O(nlog_2n),因此总时间复杂度为O(nlog_2n)

2.将长度为n的单链表链接在长度为m的单链表后面,其算法的时间复杂度是()

? ? ? ? A.O(1)? ? ? ? ? ? ? ?B.O(n)? ? ? ? ? ? ? ? C.O(m)? ? ? ? ? ? ? D.O(m+n)

? ? ? ? ?插入的时间复杂度为O(1),但是要先找到m单链表的表尾,因此时间复杂度为O(m)

3.在一个长度为n的带头结点的单链表h上,设有尾指针r,则执行()操作与链表的表长有关

A.删除单链表的第一个元素

B.删除单链表的最后一个元素

C.在单链表第一个元素前插入一个新元素

D.在单链表最后一个元素后插入一个新元素

? ? ? ? 删除单链表最后一个元素,需要把最后一个元素的前驱节点的指针域修改为NULL,因此从头遍历找到这个前驱节点,因此跟表长有关

4.?设对n(n>1)个元素的线性表的运算只有四种:删除第一个元素;删除最后一个元素;在第一个元素之前插入新元素;在最后一个元素之后插入新元素,最好使用()

A.只有尾结点指针没有头结点指针的循环单链表

B.只有尾结点指针没有头结点指针非循环双链表

C.只有头结点指针没有尾结点指针的循环双链表

D.既有头结点指针又有尾结点指针的循环单链表

? ? ? ? 首先,只要有删除最后一个元素这个条件,单链表就不适合了,不论是否带尾结点或者循环,因为要修改表尾结点的前驱节点的指针,而单链表要找前驱节点,只能遍历循环,所以直接排除单链表选项A、D

? ? ? ? 只有尾结点指针又不循环,则无法一步找到表头节点的位置,又需要遍历,因此排除选项B

(二)应用题

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-05-19 12:02:44  更:2022-05-19 12:03:21 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 1:57:44-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码