数据结构与算法(四) -- 算法示例题目

538 阅读7分钟

一、前言

学习完链表相关的知识, 这里通过示例题来对链表进行一下复习 这里使用的链表结构如下:

typedef int LBData;
typedef int Status;
//定义结点
typedef struct Node{
    LBData data;
    struct Node *next;
}Node;
typedef struct Node * LBList;

注:题目提到的链表均使用单链表, 并且有一个头节点

二、题目

2.1、题目一

题目: 将2个递增的有序链表合并为一个链表的有序链表.

要求: 链表仍然使用两个链表的空间, 不占用额外空间, 不允许重复数据.

分析:

  1. 假设链表a和b. 因为要不使用额外的空间, 所以新的链表c的指针是指向a或者b的头节点
  2. 取出a与b中第一个做比较, 小的一方则链接在c的后方,并且指向下一个节点, 大的一方则不动. 若相同, 取其中一方, 另一方释放. 重复这个步骤
  3. 假设当a为空, 则直接将剩余的b节点排在a的末尾

代码:

void MergeList(LBList *La, LBList *Lb, LBList *Lc) {
    
    LBList la = NULL, lb = NULL, lc, temp;
    
    la = (*La)->next;
    lb = (*Lb)->next;
    
    lc = *La;
    *Lc = lc;
    
    while (la && lb) {
        if ( lb->data == la->data) {
            lc->next = la;
            lc = la;
            la = la->next;
            temp = lb->next;
            free(lb);
            lb = temp;

        } else if (lb->data > la->data) {
            
            lc->next = la;
            lc = la;
            la = la->next;
            
        }else if (lb->data < la->data) {
            lc->next = lb;
            lc = lb;
            lb = lb->next;
        }
    }
    
    lc->next = la ? la : lb;
    free(*Lb);
}

2.2、题目二

题目: 已知两个链表A和B分别表示两个集合. 其元素递增排列. 设计一个算法, 用于求出A与B的交集, 并存储在A链表中;

示例: La = {2, 4, 6, 8}; Lb = {4, 6, 8, 10}; Lc = {4, 6, 8};

分析:

  1. 定义Lc指向La的头节点
  2. 遍历Lb. 判断Lb是否大于La. 是则La继续下一个节点
  3. 是否小于La, 则Lb继续下一个节点.
  4. 相等则加入到Lc中
  5. 某一个到末尾则, 则释放另一个剩余的节点

代码实现:

void Intersection(LBList *La, LBList *Lb, LBList *Lc) {
    LBList la = NULL, lb = NULL, lc, temp;
    
    la = (*La)->next;
    lb = (*Lb)->next;
    
    lc = *La;
    *Lc = lc;
    
    while (la && lb) {
        if (la->data < lb->data) {
            temp = la;
            la = la->next;
            free(temp);
            
        } if (la->data > lb->data) {
            temp = lb;
            lb = lb->next;
            free(temp);
            
        } else {
            temp = lb;
            lb = lb->next;
            free(temp);
            
            lc->next = la;
            lc = la;
            la = la->next;
        }
    }
    while (!la) {
        temp = lb;
        lb = lb->next;
        free(temp);
    }
    while (!lb) {
        temp = la;
        la = la->next;
        free(temp);
    }
    
    lc->next = NULL;
    free(*Lb);
}

2.3、题目三

题目: 设计一个算法, 将链表中所有节点的链接方向‘原地旋转‘

要求: 仅仅利用原表的存储空间, 空间复杂度为O(1)

例: L = {0, 2, 4, 6, 8, 10} , 逆转后: L = {10, 8, 6, 4, 2, 0}

分析:

  1. 逆转就相当于把最后元素依次插入 1 2 3 4...的位置, 但是依次读取最后的元素需要经历N次遍历链表, 时间复杂度太大
  2. 拿到第一个节点, 置空链表的第一个节点.
  3. 遍历链表, 将每个节点依次用头插法插入链表的第一个节点

代码:

void Inverse(LBList *L){
    LBList current = (*L)->next;//正在遍历的当前节点
    LBList first = (*L)->next;//链表的第一个节点
    (*L)->next = NULL;
    
    while (current != NULL) {
        LBList temp = current->next;
        current->next = first;
        first = current;
        current = temp;
    }
}

2.3、题目四

题目: 设计一个算法, 删除递增有序链表中值大于等于mink且小于等于maxk的所有元素

例: L = {0, 2, 4, 6, 8, 10} mink = 3; maxk = 6, 删除后: L = {0, 2, 8, 10}

分析:

  1. 依次遍历列表, 判断下一个节点值是否大大于mink, 记录此时节点A
  2. 判断下一个节点值是否小于等于maxk, 并且释放经过的节点. 如果为否则记录此时节点的下一个节点B,
  3. 将A与B链接

代码:

void DeleteMinMax(LBList *L, int mink, int maxk){
    
    LBList lb = (*L)->next;
    LBList minkNode = NULL;//, maxkNode = NULL;

    while (lb && lb->data < mink) {
        minkNode = lb;
        lb = lb->next;
    }
    while (lb && lb->data <= maxk) {
        LBList temp = lb;
        lb = lb->next;
        free(temp);
    }
    minkNode->next = lb == minkNode ? NULL : lb;
}

2.4、题目五

题目:

设将n(n>1)个整数存放到一维数组R中, 试设计一个在时间和空间两方面都尽可能高效的算法;将R中保存的序列循环左移p个位置(0<p<n)个位置, 即将R中的数据由(x0,x1,......,xn-1)变换为(xp,xp+1,...,xn-1,x0,x1,...,xp-1).

例如:

pre[10] = {0,1,2,3,4,5,6,7,8,9};n = 10,p = 3;

pre[10] = {3,4,5,6,7,8,9,0,1,2};

分析:

  1. 首先将数组反转成 9 8 7 6 5 4 3 2 1 0
  2. 将数组分割成 9 8 7 6 5 4 3 | 2 1 0
  3. 采用两极反转的办法, 分别反转两个数组 变成 3 4 5 6 7 8 9 | 0 1 2

代码:

//首先写一个用于反转数组的方法
//left: 最左边的下标, right: 最右边的下标, 依次交换左右对称的数
void Reverse(int *pre,int left ,int right){
    int l = left;
    int r = right;
    while (l < r) {
        int temp = pre[l];
        pre[l] = pre[r];
        pre[r] = temp;
        l++;
        r--;
    }
}
void LeftShift(int *pre,int n,int p){
    if (p < n && p > 0) {
        //反转原数组
        Reverse(pre, 0, n);
        //反转 0 ~ n-p
        Reverse(pre, 0, n - p);
        //反转 n-p ~ 0
        Reverse(pre, n-p, n);
    }
}

2.5、题目六

题目:

已知一个整数序列A = (a0,a1,a2,...an-1),其中(0<= ai <=n),(0<= i<=n). 若存在ap1= ap2 = ...= apm = x,且m>n/2(0<=pk<n,1<=k<=m),则称x 为 A的主元素.

设计一个尽可能高效的算法,找出数组元素中的主元素,若存在主元素则输出该元素,否则输出-1.

例如:

A = (0,5,5,3,5,7,5,5),则5是主元素; 若B = (0,5,5,3,5,1,5,7),则A 中没有主元素,假设A中的n个元素保存在一个一维数组中,

分析:

  1. 采用候选人的办法, 假设第一个元素为候选人, 保存在key中, 记录个数为1
  2. 遇到下一个元素如果与候选人一致则个数+1否则-1
  3. 如果个数变成0则把遇到的下一个元素变为候选人, 记录个数为1
  4. 遍历完成后看记录的个数是否大于0
  5. 遍历数组, 记录当前候选人是否大于总个数的一半, 大于则候选人变为主元素, 否则不存在主元素

代码:

int MainElement(int *A, int n){
    int count = 1;//个数
    int key = A[0];//候选人
    for (int i = 1; i < n; i++) {
        if (A[i] == key) {
            count++;
        } else {
            count--;
            if (count == 0) {
                key = A[i];
                count = 1;
            }
        }
    }
    //如果count >0
    if (count >0){
        //统计候选主元素的实际出现次数
        for (int i = count = 0; i < n; i++) {
            if (A[i] == key) count++;
        }
    }
    //判断count>n/2, 确认key是不是主元素
    if (count > n/2) return key;
    return -1; //不存在主元素
}

2.6、题目七

题目:

用单链表保存m个整数, 结点的结构为(data,link),且|data|<=n(n为正整数). 现在要去设计一个时间复杂度尽可能高效的算法. 对于链表中的data 绝对值相等的结点, 仅保留第一次出现的结点,而删除其余绝对值相等的结点.

例如:

链表A = {21,-15,15,-7,15}, 删除后的链表A={21,-15,-7};

分析:

  1. 采用散列表的方法. 申请一个空间用来记录每个值出是否出现过, 用值作为这个空间的下标
  2. 遍历节点, 去记录空间里查找这个节点的值是否为1
  3. 出现过则删除这个节点

代码:

void DeleteEqualNode(LBList *L,int n){
    char *p = (char *)malloc(n * sizeof(char));
    memcpy(&p, 0, n * sizeof(char));
    
    LBList r = *L;
    LBList lb = (*L)->next;
    while (lb) {
        
        if (p[abs(lb->data)]) {
            r->next = lb->next;
            free(lb);
            lb = r->next;
        } else {
            p[abs(lb->data)] = 1;
            r = lb;
            lb = lb->next;
        }
        
    }
}