【7.线性表-链式表示-王道课后算法题】

王道数据结构-第二章-链式表示算法题

1.在带头结点的单链表L中,删除所有值为x的结点,并释放其空间,假设值为x的结点不唯一,试编写算法以实现上述操作。

bool delteElement(LinkList &L, int x) {
    LNode *p = L->next;
    LNode *pre = L, *q;
    while (p != NULL) {
        if (p->data == x) {
            q=p;
            p=p->next;
            pre->next = p;
            free(q);
        } else {
            pre = p;
            p = p->next;
        }
    }
    return true;
}

2. 试编写在带头结点的单链表L中删除一个最小值结点的高效算法(假设该结点唯一)。

bool deleteMin(Linklist &L) {
    LNode *p = L->next;
    int value = 9999999;
    LNode *pre = L, *min = L->next, *preMin = L;
    while (p != NULL) {
        if (p->data < value) {
            value=p->data;
            min = p;
            preMin = pre;
            p = p->next;
        } else {
            pre = p;
            p = p->next;
        }
    }
    preMin->next = min->next;
    free(min);
    return true;
}

3. 试编写算法将带头结点的单链表就地逆置,所谓“就地”是指辅助空间复杂度为O(1)。

//头插法
bool invertList(Linklist &L) {
    LNode *p = L->next, *q ;
    L->next = NULL;
    while (p != NULL) {
        //q指向p的下一个 防止断链
        q=p->next;
        //将p插入到头结点之后
        p->next=L->next;
        L->next=p;
        p=q;
    }
    return true;
}

3.1 反向断链的方式

//反向断链 1 2 3 4
bool invertList2(Linklist &L) {
    // 空链表或只有一个节点的链表不需要反转
    if (L == NULL || L->next == NULL) {
        return true;
    }
    LNode *p = L->next;
    LNode *pre;
    LNode *r ;
    L->next = nullptr;
    while (p != NULL) {
        r = p->next; // 先保存下一个节点
        p->next = pre; // 反转当前节点的 next 指针
        pre = p; // 移动 pre 到当前节点
        p = r; // 移动 p 到下一个节点
    }
    L->next = pre; // 将头节点的 next 指向新的头节点
    return true;
}

4. 设在一个带表头结点的单链表中,所有结点的元素值无序,试编写一个函数,删除表中所有介于给定的两个值(作为函数参数给出)之间的元素(若存在)。

bool deleteBetween(Linklist &L, int x, int y) {
    if (x >= y) {
        return false;
    }
    if (L->next == nullptr) {
        return false;
    }
    LNode *p = L->next, *pre = L, *q;
    while (p != nullptr) {
        if (x <= p->data && p->data <= y) {
            q = p;
            p = p->next;
            pre->next = p;
            free(q);
        } else {
            pre = p;
            p = p->next;
        }
    }
    return true;
}

5. 给定两个单链表,试分析找出两个链表的公共结点的思想(不用写代码)。

  • 两个单链表有公共结点,即两个链表从某一结点开始,它们的 next 都指向同一结点。由于每
    个单链表结点只有一个 next 域,因此从第一个公共结点开始,之后的所有结点都是重合的,不可
    能再出现分叉。所以两个有公共结点而部分重合的单链表,拓扑形状看起来像Y,而不可能像X
  • 本题极容易联想到“蛮”方法:在第一个链表上顺序遍历每个结点,每遍历一个结点,在第
    二个链表上顺序遍历所有结点,若找到两个相同的结点,则找到了它们的公共结点。显然,该算
    法的时间复杂度为O(lenlxlen2)。
  • 接下来我们试着去寻找一个线性时间复杂度的算法。先把问题简化:如何判断两个单向链表
    有没有公共结点?应注意到这样一个事实:若两个链表有一个公共结点,则该公共结点之后的所
    有结点都是重合的,即它们的最后一个结点必然是重合的。因此,我们判断两个链表是不是有重
    合的部分时,只需要分别遍历两个链表到最后一个结点。若两个尾结点是一样的,则说明它们有
    公共结点,否则两个链表没有公共结点。
    然而,在上面的思路中,顺序遍历两个链表到尾结点时,并不能保证在两个链表上同时到达
    尾结点。这是因为两个链表长度不一定一样。但假设一个链表比另一个长k个结点,我们先在长
    的链表上遍历k个结点,之后再同步遍历,此时我们就能保证同时到达最后一个结点。由于两个
    链表从第一个公共结点开始到链表的尾结点,这一部分是重合的,因此它们肯定也是同时到达第
    一公共结点的。于是在遍历中,第一个相同的结点就是第一个公共的结点。
  • 根据这一思路中,我们先要分别遍历两个链表得到它们的长度,并求出两个长度之差。在长
    的链表上先遍历长度之差个结点之后,再同步遍历两个链表,直到找到相同的结点,或者一直到
    链表结束。此时,该方法的时间复杂度为 O(lenl +len2)。

6. 设C={a1,b1,a2,b2,…,an,bn}为线性表,采用带头结点的单链表存放,设计一个就地算法,将其拆分为两个线性表,使得A{a1,a2,…,an},B={bn,…,b2,b1}。

bool splitList(Linklist &L, Linklist &L2) {
    //p指向首元结点 r是为指针
    LNode *p = L->next, *r = L, *q;
    L2 = (LNode *) malloc(sizeof(LNode));
    L2->next = NULL;
    int count = 1;
    while (p != NULL) {
        if (count % 2 != 0) {
            r->next = p;
            r = p;
            p = p->next;
        } else {
            q=p->next;
            p->next=L2->next;
            L2->next = p;
            p = q;
        }
        count++;
    }
    //第一个链表最后r尾指针指向NULL
    r->next = NULL;
    return true;
}

7. 在一个递增有序的单链表中,存在重复的元素。设计算法删除重复的元素,例如(7,10,10,21,30,42,42,42,51,70)将变为(7,10,21,30,42,51,70)。

bool deleteRepeat(Linklist &L) {
    LNode *p = L->next,*q;
    //这里判断条件为p->next!=NULL是为了让q有意义
    while (p->next!=NULL){
        q=p->next;
        //如果相同就删除当前相同元素的后一个 直到没有相同为止
        if (p->data==q->data){
            p->next=q->next;
            free(q);
        }else{
            p=p->next;
        }
    }
    return true;
}

8. 设A和B是两个单链表(带头结点),其中元素递增有序。设计一个算法从A和B中的公共元素产生单链表 C,要求不破坏 A、B的结点。

//尾插法插入新结点
 Linklist getCommonList(Linklist &L1, Linklist &L2){
     Linklist commonList=(LNode *) malloc(sizeof(LNode));
    commonList->next=NULL;
    LNode *p1=L1->next,*p2=L2->next,*r=commonList,*q;
    while (p1!=NULL&&p2!=NULL){
        if (p1->data<p2->data){
            p1=p1->next;
        }else if (p1->data>p2->data){
            p2=p2->next;
        }
        if (p1->data==p2->data){
            LNode *s=(LNode *) malloc(sizeof(LNode));
            s->data=p1->data;
            r->next=s;
            r=s;
            p1=p1->next;
            p2=p2->next;
        }
    }
    r->next=NULL;
    return commonList;
 }

9. 已知两个链表A和B分别表示两个集合,其元素递增排列。编制函数,求A与B的交集,并存放于A链表中。

//谁小就free谁直到遇到相同的 和第9题思路差不多
  Linklist unionList(Linklist &L1, Linklist &L2){
      LNode *p1=L1->next,*p2=L2->next,*r=L1,*q;
      while (p1&&p2){
          if (p1->data==p2->data){
              //尾插法 r指向p1
              r->next=p1;
              //更新尾指针
              r=p1;
              p1=p1->next;
              //删除掉p2
              q=p2;
              p2=p2->next;
              free(q);
          }else if (p1->data<p2->data){
              //删除p1
              q=p1;
              p1=p1->next;
              free(q);
          }else if (p1->data>p2->data){
              q=p2;
              p2=p2->next;
              free(q);
          }

      }
      //假设找完交集还有剩余元素全部free
     while (p1){
         q=p1;
         p1=p1->next;
         free(q);
     }
     while (p2){
         q=p2;
         p2=p2->next;
         free(q);
     }
     r->next=NULL;
     //freeL2
     free(L2);
     return L1;
  }

10. 两个整数序列A=a1,a2?a3,…,am和B=b1,b2,b3,…,bn已经存入两个单链表中,设计一个算法,判断序列B是否是序列 A的连续子序列。

bool continueList(Linklist &L1, Linklist &L2) {
    LNode *p1 = L1->next, *p2 = L2->next, *q = L1->next;
    while (p1 != NULL && p2 != NULL) {
        if (p1->data == p2->data) {
            p1 = p1->next;
            p2 = p2->next;
        } else {
            p1 = p1->next;
            p2 = L2->next;
        }
    }
    if (p2 == NULL) {
        return true;
    } else {
        return false;
    }
}

11. 设计一个算法用于判断带头结点的循环双链表是否对称。

# include "istream"

using namespace std;
typedef struct LNode {
    int data;
    struct LNode *prior, *next;
} LNode, *LinkList;

bool initList(LinkList &L) {
    L = (LNode *) malloc(sizeof(LNode));
    L->next = L;
    L->prior = L;
    return true;
}

void printList(LinkList &head) {
    if (head == nullptr) {
        cout << "空链表" << endl;
        return;
    }
    LNode *p = head->next;
    while (p != head) {
        cout << p->data << " ";
        p = p->next;
    }
}

bool insertNode(LinkList &L) {
    LNode *p = L->next;
    int values[] = {1, 2, 3, 2, 1};
    int n = sizeof(values) / sizeof(values[0]);
    for (int i = 0; i < n; i++) {
        LNode *s = (LNode *) malloc(sizeof(LNode));
        s->data = values[i];
        s->next = p->next;
        s->prior = p;
        p->next->prior = s;
        p->next = s;
    }
    return true;
}

/**
 * 设计一个算法用于判断带头结点的循环双链表是否对称。
 * @return
 */
bool isSymmetry(LinkList &L) {
    if (L == nullptr || L->next == nullptr) {
        return false;
    }
    LNode *p = L->next;
    LNode *q = L->prior;
    /**
     * p==q说明他们指向同一个中间位置,p->next=-q;说明他们相邻,因此只要不是这种情况就继续循环
     */
    while (p != q && p->next != q) {
        if (p->data == q->data) {
            p = p->next;
            q = q->prior;
        } else {
            return false;
        }
    }
    return true;
}

int main() {
    LinkList L;
    initList(L);
    insertNode(L);
    bool e = isSymmetry(L);
    cout << e << endl;
//    printList(L);
    return 0;
}

12. 有两个循环单链表,链表头指针分别为h1和h2,编写一个函数将链表h2链接到链表h1之后,要求链接后的链表仍保持循环链表形式。

#include "iostream"

using namespace std;
typedef struct LNode {
    int data;
    struct LNode *next;
} LNode, *LinkList;

bool initList(LinkList &L) {
    L = (LNode *) malloc(sizeof(LNode));
    L->next = L;
    return true;
}

bool insertList(LinkList &L) {
    int values[] = {1, 2, 3, 4, 5};
    int len = sizeof(values) / sizeof(values[0]);
    for (int i = 0; i < len; i++) {
        LNode *s = (LNode *) malloc(sizeof(LNode));
        s->data = values[i];
        s->next = L->next;
        L->next = s;
    }
    return true;
}

bool insertList2(LinkList &L) {
    int values[] = {6, 7, 8, 9, 10};
    int len = sizeof(values) / sizeof(values[0]);
    for (int i = 0; i < len; i++) {
        LNode *s = (LNode *) malloc(sizeof(LNode));
        s->data = values[i];
        s->next = L->next;
        L->next = s;
    }
    return true;
}

void printList(LinkList L) {
    LNode *p = L->next;
    while (p != L) {
        printf("%d ", p->data);
        p = p->next;
    }
    printf("\n");
}

/**
* 12.有两个循环单链表,链表头指针分别为h1和h2,编写一个函数将链表h2链接到链表h1之后,要求链接后的链表仍保持循环链表形式。
*/
LinkList getLinkList(LinkList &L1, LinkList &L2) {
    LNode *p1 = L1->next,*q = L1;
    LNode *p2 = L2->next, *head = L2;
    while (p1->next != q) {
        p1 = p1->next;
    }
    p1->next =p2;
    while (p2->next != head) {
        p2 = p2->next;
    }
    p2->next = L1;
    return L1;
}

int main() {
    LinkList h1, h2;
    initList(h1);
    initList(h2);
    insertList(h1);
    insertList2(h2);
    printList(h1);
    printList(h2);
    LinkList list=getLinkList(h1, h2);
    cout<<"============================"<<endl;
    printList(list);
    return 0;
}

13. 设有一个带头结点的非循环双链表 L,其每个结点中除有 pre、data 和 next 域外,还有一个访问频度域 freq,其值均初始化为零。每当在链表中进行一次 Locate(L,x)运算时,令值为x的结点中freq域的值增1,并使此链表中的结点保持按访问频度递减的顺序排列,且最近访问的结点排在频度相同的结点之前,以便使频繁访问的结点总是靠近表头。试编写符合上述要求的 Locate(L,x)函数,返回找到结点的地址,类型为指针型。

/**
 * 算法思想:首先在双向链表中查找数据值为x的结点,查到后,将结点从链表上摘下,然后
 * 顺着结点的前驱链查找该结点的插入位置(频度递减,且排在同频度的第一个,即向前找到第一
 * 个比它的频度大的结点,插入位置为该结点之后),并插入到该位置。
 */
 LinkList Locate(LinkList &L, int x) {
    //p为工作指针,q为p的前驱,用于查找插入位置
    LNode *p = L->next, *q;
    while (p && p->data != x) {
        //找到值为x的结点
        p = p->next;
    }
    if (!p) {
        exit(0);//找不到
    } else {
        //频度+1
        p->freq++;
        //刚好这个值就是第一个结点或者他就在属于自己的频度位置上
        if (p->prior == L || p->prior->freq > p->freq) {
            return p;
        }
        //p非最后一个结点
        if (p->next != NULL)
            //把p结点摘下来
            p->next->prior = p->prior;
        p->prior->next = p->next;
        //q指向p的前驱
        q = p->prior;
        //q不是头结点且 freq小于p的freq
        //让p一直前移直到到达属于自己的位置
        while (q != L && q->freq <= p->freq) {
            q = q->prior;
            p->next = q->next;
            if (q->next != NULL)
                //结点p排在同频度第一个
                q->next->prior = p;
            p->prior = q;
            q->next = p;
        }
        return p;
    }
}

14. 14.设将n(n>1)个整数存放到不带头结点的单链表L中,设计算法将L中保存的序列循环右移k(0<k<n)个位置。例如,若k=1,则将链表{0,1,2,3}变为{3,0,1,2}。要求:1)给出算法的基本设计思想。2)根据设计思想,采用C或C++语言描述算法,关键之处给出注释。3)说明你所设计算法的时间复杂度和空间复杂度。

//思路连接成一个环:要偏移k个单位,所以只需要找到第k+1个位置作为首节点即可。
Linklist rightShift(Linklist &L, int k) {
    LNode *p = L,*r=L,*q;
    int count = 0;
    while (p!=NULL) {
        q=p;
        p = p->next;
        count++;
    }
    //变成单循环链表
    q->next = r;
    k = k % count;
    p=r;
    for (int i = 1; i < count - k; ++i) {
        p = p->next;
    }
    L = p->next;
    p->next = NULL;
    return L;
}

15. 单链表有环,是指单链表的最后一个结点的指针指向了链表中的某个结点(通常单链表的最后一个结点的指针域是空的)。试编写算法判断单链表是否存在环。1)给出算法的基本设计思想。2)根据设计思想,采用C或C+语言描述算法,关键之处给出注释。3)说明你所设计算法的时间复杂度和空间复杂度。

LNode* hasLoop(Linklist &L) {
    LNode *fast = L, *slow = L;
    while (fast != NULL && fast->next != NULL) {
        fast = fast->next->next;
        slow = slow->next;
        if (fast == slow){
            break;
        }
    }
    if (fast== nullptr|| fast->next== nullptr){
        return NULL;
    }
    LNode *p1 = L,*p2=slow;
    while (p1!=p2){
        p1=p1->next;
        p2=p2->next;
    }
    return p1;
}

16. 设有一个长度n(n为偶数)的不带头结点的单链表,且结点值都大于0,设计算法求这个单链表的最大孪生和。孪生和定义为一个结点值与其孪生结点值之和,对于第i个结点(从0开始),其孪生结点为第n-i-1个结点。要求:1)给出算法的基本设计思想。2)根据设计思想,采用C或C++语言描述算法,关键之处给出注释。3)说明你的算法的时间复杂度和空间复杂度。

int pairSum(Linklist &L){
    LNode *fast = L->next, *slow = L;
    while (fast != NULL && fast->next != NULL){
        fast = fast->next->next;
        slow = slow->next;
    }
    LNode *head=NULL,*p=slow->next,*q;
    //头插法
    while (p!=NULL){
        q = p->next;
        p->next=head;
        head=p;
        p=q;
    }
    int maxSum = 0;
    p=L;
    LNode *k=head;
    //head指向中间位置
    while (k!=NULL){
        if (p->data+k->data>maxSum)
            maxSum=p->data+k->data;
        p = p->next;
        k= k->next;
    }
    return maxSum;
}

17. 已知一个带有表头结点的单链表,结点结构为data link 假设该链表只给出了头指针 list。在不改变链表的前提下,请设计一个尽可能高效的算法,查找链表中倒数第k个位置上的结点(k为正整数)。若查找成功,算法输出该结点的 data域的值,并返回 1;否则,只返回0。要求:1)描述算法的基本设计思想。2)描述算法的详细实现步骤。3)根据设计思想和实现步骤,采用程序设计语言描述算法(使用 C、C++或 Java 语言实现),关键之处请给出简要注释。

思路:快慢指针,快指针先走k步,然后快慢指针同时走,当快指针到达链表尾部时,慢指针指向的结点就是倒数第k个结点。
因为q和p总是差k个单位,当p走完列表时,q指向的结点就是倒数第k个结点。
int findKthToTail(Linklist &L, int k) {
    LNode *p = L->next, *q = L->next;
    int count = 0;
    while (p != NULL) {
        if (count < k) {
            count++;
        } else {
            q = q->next;
        }
        p = p->next;
    }
    //count的长度不足k,返回0,找不到
    if (count < k) {
        return 0;
    } else {
        cout << q->data << endl;
    }
    return 1;
}

18. 18.假定采用带头结点的单链表保存单词,当两个单词有相同的后级时,可共享相同的后缀存储空间,例如,loading和being.的存储映像如下图所示。设str1和str2分别指向两个单词所在单链表的头结点,链表结点结构为data next请设计一个时间上尽可能高效的算法,找出由str1和str2所指向两个链表共同后级的起始位置(如图中字符i所在结点的位置p)。要求:1)给出算法的基本设计思想。2)根据设计思想,采用C或 C++或Java语言描述算法,关键之处给出注释。3)说明你所设计算法的时间复杂度。

/**
 * 思路: 顺序遍历两个链表到尾结点时,并不能保证两个链表同时到达尾结点。这是因为两个链表的
 * 长度不同。假设一个链表比另一个链表长k个结点,我们先在长链表上遍历k个结点,之后同步
 * 遍历两个链表,这样就能够保证它们同时到达最后一个结点。因为两个链表从第一个公共结点到
 * 链表的尾结点都是重合的,所以它们肯定同时到达第一个公共结点。
 */
 int getLength(Linklist &L) {
    LNode *p = L->next;
    int count = 0;
    while (p != NULL) {
        count++;
        p = p->next;
    }
    return count;
}
LNode *findCommon(Linklist &L1, Linklist &L2) {
    LNode *p1 = L1->next, *p2 = L2->next;
    int size1 = getLength(L1);
    int size2 = getLength(L2);
    if (size1 > size2) {
        int count = 0;
        while (p1 != NULL && (size1 - size2) != count) {
            p1 = p1->next;
            count++;
        }
    } else if (size1 < size2) {
        int count = 0;
        while (p2 != NULL && (size1 - size2) != count) {
            p2 = p2->next;
            count++;
        }
    }
    while (p1->next != NULL && p1->next != p2->next) {
        p1 = p1->next;
        p2 = p2->next;
    }
    return p1->next;
}

19. 19.用单链表保存m个整数,结点的结构为[data][link],且data|≤n(n为正整数)。现要求设计一个时间复杂度尽可能高效的算法,对于链表中 data 的绝对值相等的结点,仅保留第一次出现的结点而删除其余绝对值相等的结点。例如,若给定的单链表 head 如下:1)给出算法的基本设计思想。2)使用 C或 C++语言,给出单链表结点的数据类型定义。3)根据设计思想,采用C或 C+语言描述算法,关键之处给出注释。4)说明你所设计算法的时间复杂度和空间复杂度。

在这里插入图片描述

int getAbs(int x) {
    return x >= 0 ? x : -x;
}
bool deleteAbsRepeat(Linklist &L, int n) {
    LNode *p = L, *q;
    int arr[22] = {};
    int m;
    for (int i = 0; i < n + 1; ++i) {
        arr[i] = 0;
    }
    while (p->next!= NULL) {
        m = getAbs(p->next->data);
        if (arr[m] == 0) {
            arr[m] = 1;
            p = p->next;
        } else {
            q = p->next;
            p->next = q->next;
            free(q);
        }
    }
    return true;
}

20. 设线性表L=(a1,a2,a3,…,an-2,an-1,an)采用带头结点的单链表保存,链表中的结点定义如下请设计一个空间复杂度为 O(1)且时间上尽可能高效的算法,重新排列L中的各结点,得到线性表L’=(a1,an,a2,an-1,a3,an-2)。要求:1)给出算法的基本设计思想。2)根据设计思想,采用C或C++语言描述算法,关键之处给出注释。3)说明你所设计的算法的时间复杂度。

bool reverseList(Linklist &L) {
    LNode *p =L, *q = L,*r,*s;
    while (q->next != NULL) {
        p=p->next;
        q=q->next;
        if (q->next != NULL){
            q=q->next;
        }
    }
    q=p->next;
    p->next=NULL;
    while (q != NULL){
        r=q->next;
        q->next=p->next;
        p->next=q;
        q=r;
    }
    s=L->next;
    q=p->next;
    p->next=NULL;
    while (q != NULL){
        r=q->next;
        q->next=s->next;
        s->next=q;
        s=q->next;
        q=r;
    }
    return 1;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

青北念

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

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

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

打赏作者

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

抵扣说明:

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

余额充值