【考研】单链表相关算法(从基础到真题)

34 篇文章 55 订阅
21 篇文章 1 订阅

CSDN话题挑战赛第2期
参赛话题:学习笔记

前言

本文主要为了掌握单链表基础操作所作的总结,习题部分含408真题或高校真题,以便考研复习。包括但不限于尾插法和前插法建立单链表、“就地逆置”、链式有序表的合并等等。(后期也会继续补充题目。)

注意:

(1)除非特别说明,本文的单链表操作默认是带头结点的。

(2)区分带头结点和不带头结点的单链表的博文,我写了一半,写完就放链接。(如果很久还没放链接,可能博主有事耽搁了,麻烦提醒一下)

(3)可搭配以下链接一起学习:

【考研】线性表的应用之有序表的合并_住在阳光的心里的博客-CSDN博客

【考研】《数据结构》知识点总结.pdf_其它文档类资源-CSDN文库 

 

目录

前言

一、 基本操作

1、单链表的结构体定义

2、初始化单链表 InitLinkList()

3、尾插法建立单链表 ListInsert_R()

4、 头插法建立单链表 ListInsert_H()

5、单链表的“就地逆置” ReverseList()

6、逆序打印单链表中的结点 PrintList()

7、删除递增单链表中重复的元素 Del_Duplicate1()

8、删除单链表中值为 x 的元素的所有结点 Del_Duplicate2()

9、查找值为 x 的元素并删除它 FindDel()

10、删除单链表中的最小值 Del_Min()

11、按序号查找结点值 GetElem()

12、链式有序表的合并 MergeList_L()

13、将一个数据域为整数的单链表分割为两个分别为奇数和偶数的链表 Split()

14、查找链表中倒数第 k 个结点 Find_k_Last()

二、习题


一、 基本操作

1、单链表的结构体定义

// 单链表的存储结构
typedef struct LNode{
    ElemType data;   //结点的数据域
    struct LNode *next;   //结点的指针域
}LNode, *LinkList;    // LinkList 为指向结构体 LNode 的指针类型

2、初始化单链表 InitLinkList()

//初始化单链表
void InitLinkList(LinkList &L){
    L = (LNode *)malloc(sizeof(LNode));
    L->next = NULL:
}

3、尾插法建立单链表 ListInsert_R()

//尾插法
void ListInsert_R(LinkList &L, int a[], int n){
    InitLinkList(L);    //初始化单链表
    /*
    L = (LNode *)malloc(sizeof(LNode));
    L->next = NULL;
    */

    LNode *s, *rear = L;  // s 用来指向新申请的结点,rear 指向单链表 L 的终端结点
    
    for(int i = 0; i < n; i++){
        s = (LNode *)malloc(sizeof(LNode));
        s->data = a[i];    //要插入的结点值
        rear->next = s;
        rear = rear->next;   //更新 rear,指向 L 的终端结点
    }

    rear->next = NULL;  
}

4、 头插法建立单链表 ListInsert_H()

//头插法
void ListInsert_H(LinkList &L, int a[], int n){
    LNode *s;  // s 用来指向新申请的结点
    InitLinkList(L);   //初始化单链表

    for(int i = 0; i < n; i++){
        s = (LNode *)malloc(sizeof(LNode));
        s->data = a[i];
        s->next = L->next;
        L->next = s;
    }
}

5、单链表的“就地逆置” ReverseList()

//用头插法实现
void ReverseList(LinkList L){
    LNode *p = L->next, *q;   // q 起到暂存结点作用
    L->next = NULL;   //只留下头结点

    while(p != NULL){
        q = p->next;  

        p->next = L->next;  //头插法
        L->next = p;

        p = q; //得到下一个结点
    }
}

6、逆序打印单链表中的结点 PrintList()

void PrintList(LinkList L){
    if(L != NULL){
        PrintList(L->next);  //递归
        printf("%d ", L->data);
    }
}

7、删除递增单链表中重复的元素 Del_Duplicate1()

void Del_Duplicate1(LinkList &L){
    LNode *p, *q;  //q 用来释放被删除的元素
    p = L->next;

    while(p->next != NULL){
        if(p->data == p->next->data){
            q = p->next;
            p->next = q->next;
            free(q);
        }
        else{
            p = p->next;
        }
    }
}

8、删除单链表中值为 x 的元素的所有结点 Del_Duplicate2()

void Del_Duplicate2(LinkList &L, int x) { 
    LinkList p, q = L; 
    p = list->next;     //先从链表的第 2 个结点开始判断值是否为 x

    while (p != NULL) { 
        if (p->data == x){   
            q->next = p->next; 
            free(p);    //释放 p 的空间
            p = q->next; 
        } 
        else{ 
            q = p; 
            p = p->next; 
        } 
    } 

    if (list->data == x){   //判断第一个结点值是否为 x
        q = L; 
        L= L->next; 
        free(q); 
    } 
} 

9、查找值为 x 的元素并删除它 FindDel()

int FindDel(LinkList &L, int x){
    //找元素 x
    LNode *p;
    p = L;
    while(p->next != NULL){
        if(p->next->data == x){
            break;
        }
        p = p->next;
    }

    //删除它
    if(p->next->data == NULL){   //没找到
        return 0;
    }
    else{
        LNode *del;   //暂存要被删的结点x
        del = p->next;
        p->next = del->next;
        free(del);
        return 1;
    }
}

10、删除单链表中的最小值 Del_Min()

void Del_Min(LinkList &L){
    LNode *p, *pre, *minp, *premin;
    p = L->next;   // p 用来扫描链表
    pre = L;   // pre 指向 p 的前驱
    minp = p;    // minp 指向最小值结点
    premin = pre;  // premin 指向 minp 的前驱

    while(p){
        if(p->data < minp->data){
            minp = p;
            premin = pre;
        }
        pre = p;
        p = p->next;
    }

    premin->next = minp->next;
    free(minp);
}

11、按序号查找结点值 GetElem()

//从单链表中的第一个结点出发,顺指针 next 域逐个往下搜索,
//直到找到第 i 个结点为止,否则返回最后一个结点指针域 NULL
void *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){   //从第 i 个结点开始找,查找第 i 个结点
        p = p->next;
        j++;
    }
    return p;  
}

12、链式有序表的合并 MergeList_L()

//已知单链表 LA 和 LB 的元素按值非递减排列
//归并 LA 和 LB 得到新的单链表 LC, LC 的元素也按值非递减排列
void MergeList_L(LinkList &LA, LinkList &LB, LinkList &LC){
    //pa 和 pb 的初值分别指向两个表的第一个结点
    pa = LA->next; 
    pb = LB->next;
 
    LC = LA;      //用 LA 的头结点作为 LC 的头结点
    pc = LC;     //pc 的初值指向 LC 的头结点
 
    while (pa && pb){    //LA和LB均未到达表尾,依次“摘取”两表中值较小的结点插入到 LC 的最后
        if (pa->data <= pb->data){    //“摘取” pa 所指结点
            pc->next = pa;     //将 pa 所指结点链接到 pc 所指结点之后
            pc = pa;    //pc 指向 pa
            pa = pa->next;    //pa 指向下一结点
        }
        else{    //“摘取” pb 所指结点
            pc->next = pb;      //将 pb 所指结点链接到 pc 所指结点之后
            pc = pb;     //pc 指向 pb
            pb = pb->next;    //pb 指向下一结点
        }
    }
 
    pc->next = pa?pa:pb;    //将非空表的剩余段插入到 pc 所指结点之后
    delete LB;      //释放 LB 的头结点
}

13、将一个数据域为整数的单链表分割为两个分别为奇数和偶数的链表 Split()

void Split(LinkList A, LinkList &B){
    LNode *p, *q, *r;
    B = (LNode *)malloc(sizeof(LNode));
    B->next = NULL;

    p = A;
    r = B;

    while(p){
        if(p->next->data % 2 == 0){ // p->next 为偶数
            q = p->next;
            p->next = q->next;  //尾插法
            r->next = q;
            r = q;   //更新 r,让 r 指向新的终端结点
            q->next = NULL;
        }
        else{
            p = p->next;
        }
    }
}

14、查找链表中倒数第 k 个结点 Find_k_Last()

int Find_k_Last(LinkList L, int k){
    LNode *p, *q;
    p = L->next;
    q = L;
    int i = 1;
    while(p != NULL){
        p = p->next;
        ++i;
        if(i > k){
            q = q->next;
        }
    }

    if(q == L) return 0;  //链表的结点数小于 k
    else{
        printf("%d ", p->data);
        return 1;
    }
}

 

二、习题

1、有两个带头节点的降序单链表 L1、L2,较长的链表元素个数为 n。链表结点定义如下所示。请设计一个时间上尽可能高效算法,按升序输出两个链表中所有元素的权值。

解:(1)算法思想

将两链表归并到链表 L3 中,归并时采用头插法(输入顺序与线性表中的逻辑顺序是相反的),得到的降序链表 L3,依次输出链表中的元素。

(2)代码如下

typedef struct LinkNode {
    double data;      //权值
    struct LinkNode *next;      //指向单链表的下一个结点
} LinkNode, *Linklist;
 
 
LinkNode L3; //定义全局变量 L3
Linklist Head_Insert(Linklist p){    //头插法插入 L3 中
    Linklist p1 = p->next;
    p->next = L3->next;
    L3->next = p;
    p = p1;
    return p;
}
 
void ans(Linklist L1, L2){
    int t = 0; //t 是新数组中元素个数
    Linklist p = L1->next;    //p 指向 L1 链表的第一个元素
    Linklist q = L2->next;    //q 指向 L2 链表的第一个元素
    L3->next = null;    //初始链表 L3 为空
 
    while (p != null && q != null){    //归并两个降序链表,每次选较大元素
        if (p->data > q->data)
            p = Head_Insert(p);    //将 p 头插法插入 L3 中,p 指向下一个结点
        else
            q = Head_Insert(q);    //将 q 头插法插入 L3 中,q 指向下一个结点
    }
 
    while (p != null){    //如果 L1 链表中有剩余元素则头插法插入 L3
        p = Head_Insert(p);     //将 p 头插法插入 L3 中,p 指向下一个结点
 
    while (q != null)     //如果 L2 链表中有剩余元素则头插法插入 L3
        q = Head_Insert(q);     //将 q 头插法插入 L3 中,q 指向下一个结点
 
    p = L3->next;
 
    while (p != null){      //依次输出链表 L3 中的元素
        cout << p->data;
         p=p->next;
    }
}

 2、将两个递增有序链表合并为一个递增的有序链表。要求结果链表仍使用原来两个链表的存储空间, 不另外占用其它的存储空间。表中不允许有重复的数据。

解:(1)算法思想

合并后的新表使用头指针 Lc 指向,pa 和 pb 分别是链表 La 和 Lb 的工作指针,初始化为相应链表的第一个结点,从第一个结点开始进行比较。

当两个链表 La 和 Lb 均为到达表尾结点时,依次摘取其中较小者重新链接在 Lc 表的最后。

如果两个表中的元素相等,只摘取 La 表中的元素,删除 Lb 表中的元素,这样确保合并后表中无重复的元素。

当一个表到达表尾结点,为空时,将非空表的剩余元素直接链接在 Lc 表的最后。 

(2)代码如下

//合并链表 La 和 Lb,合并后的新表使用头指针 Lc 指向
void MergeList(LinkList &La, LinkList &Lb, LinkList &Lc){
    // pa 和 pb 分别是链表 La 和 Lb 的工作指针,初始化为相应链表的第一个结点
    pa = La->next;  
    pb = Lb->next;    
   
    Lc = pc = La;   //用 La 的头结点作为 Lc 的头结点

    while(pa && pb){  //取较小者 La 中的元素,将 pa 链接在 pc 的后面,pa 指针后移
        if(pa->data < pb->data){
            pc->next = pa;
            pc = pa;
            pa = pa->next;
        }
        else if(pa->data > pb->data) { //取较小者Lb中的元素,将pb链接在pc的后面,pb指针后移
            pc->next = pb; 
            pc = pb; 
            pb = pb->next;
        }
        else{     //相等时取La中的元素,删除Lb中的元素
            pc->next = pa;
            pc = pa;
            pa = pa->next;

            q = pb->next;
            delete pb;    //确保合并后表中无重复的元素
            pb = q;
        }
    }
    pc->next = pa ? pa : pb;    //插入剩余段
    delete Lb;            //释放 Lb 的头结点
}  

3、设计一个算法,通过一趟遍历在单链表中确定值最大的结点。

解:(1)算法思想

假定第一个结点中数据具有最大值,依次与下一个元素比较,若其小于下一个元素,则设其下一个元素为最大值,反复进行比较,直到遍历完该链表。

(2)代码如下

int Max (LinkList L){
	if(L->next == NULL) 
        return NULL;

    LNode *pmax, *p;
	pmax = L->next; //假定第一个结点中数据具有最大值
	p = L->next->next;

	while(p != NULL){  //如果下一个结点存在
		if(p->data > pmax->data) 
            pmax = p;    //如果 p 的值大于 pmax 的值,则重新赋值
		p = p->next;   //遍历链表
	}
	return pmax->data;
}

4、设计一个算法,删除递增有序链表中值大于 mink 且小于 maxk 的所有元素(mink 和 maxk 是给定的两个参数,其值可以和表中的元素相同,也可以不同 )。

解:(1)算法思想

分别查找第一个值 > mink 的结点和第一个值 ≥ maxk 的结点,再修改指针,删除值大于 mink 且小于 maxk 的所有元素。

(2)代码如下

void delete(LinkList &L, int mink, int maxk){
   p = L->next; //首元结点
   while (p && p->data <= mink){  //查找第一个值 > mink的结点
       pre = p;  
       p = p->next; 
   } 

   if (p){
       while (p && p->data < maxk)  // 查找第一个值 ≥ maxk 的结点
          p = p->next;
                    
      q = pre->next;   
      pre->next = p;  // 修改指针

      while (q != p){ 
          s = q->next;  
          delete q;  // 释放结点空间
          q = s; 
      } 
   }
}

5、设线性表L1 = (a1, a2, a3, ...., an-2, an-1, an ) 采用带头结点的单链表保存,链表中的结点定义如下:

typedef struct node{
    int data;
    struct node*next;
}NODE;

请设计一个空间复杂度为 O(1) 且时间上尽可能高效的算法,重新排列 L1 中的各结点,得到线性表 L2 = (a1, an, a2, an-1, a3, an-2, ....),要求:

(1)给出算法的基本设计思想。 

(2)根据设计思想,采用 C 或 C++语言描述算法,关键之处给出注释。 

(3)说明你所设计的算法的时间复杂度。

解:(1)算法思想

先观察 L1 = (a1, a2, a3, ...., an-2, an-1, an ) 和L2 = (a1, an, a2, an-1, a3, an-2, ....)

发现 L2 是由 L1摘 取第一个元素,再摘取倒数第一个元素……依次合并而成的。

为了方便链表后半段取元素,需要先将 L1 后半段原地逆置[题目要求空间复杂度为 O(1),不能借助栈],否则每取最后一个结点都需要遍历一次链表。

① 先找出链表 L1 的中间结点,为此设置两个指针 p 和 q,指针 p 每次 走一步,指针 q 每次走两步,当指针 q 到达链尾时,指针 p 正好在链表的中间结点;

② 然后将 L1 的后半段结点原地逆置。

③ 从单链表前后两段中依次各取一个结点,按要求重排。

(2)代码如下

void change_list(NODE *h)
{
    NODE *p, *q, *r, *s;
    p = q = h;
    while (q->next != NULL) //寻找中间结点
    {
        p = p->next; //p 走一步
        q = q->next;
        if (q->next != NULL)
            q = q->next; //q 走两步
    }
    q = p->next; //p 所指结点为中间结点,q 为后半段链表的首结点
    p->next = NULL;
    while (q != NULL) //将链表后半段逆置
    {
        r = q->next;
        q->next = p->next;
        p->next = q;
        q = r;
    }
    s = h->next; //s 指向前半段的第一个数据结点,即插入点
    q = p->next; //q 指向后半段的第一个数据结点
    p->next = NULL;
    while (q != NULL) //将链表后半段的结点插入到指定位置
    {
        r = q->next;       //r 指向后半段的下一个结点
        q->next = s->next; //将 q 所指结点插入到 s 所指结点之后
        s->next = q;
        s = q->next; //s 指向前半段的下一个插入点
        q = r;
    }
}

答案来源链接:牛客网 (nowcoder.com)
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

住在阳光的心里

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

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

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

打赏作者

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

抵扣说明:

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

余额充值