数据结构与算法 线性表习题

2020-04-13  本文已影响0人  今年27

题目:
将2个递增的有序链表合并为一个有序链表; 要求结果链表仍然使用两个链表的存储空间,不另外占用其他的存储空间. 表中不允许有重复的数据

关键词:递增有序链表,不允许有重复数据,保留递增关系(后插法)
不占用额外的存储空间指的是不能开辟新节点,赋值在链接到链表上;

int combine(LinkList *L1, LinkList *L2, LinkList* C){
    LinkList p1 = *L1;
    LinkList p2 = *L2;
    LinkList pc;
    p1 = p1 -> next;
    p2 = p2 -> next;
    *C = *L1;//合并后的数组
    pc = *L1;
    while (p1 && p2) {
        
        if (p1 -> data < p2 -> data) { //如果第一个链表的结点小于第二个链表的结点
            pc -> next = p1;
            p1 = p1 -> next;
            pc = pc -> next;
        }else if (p1 -> data > p2 -> data) {//第二个链表的结点小于第一个链表的结点
            pc -> next = p2;
            p2 = p2 -> next;
            pc = pc -> next;
        }else{//相等这种情况,需要去释放其中一个结点
            pc -> next = p1;
            p1 = p1 -> next;
            LinkList tem = p2;//用一个临时变量记录这个存在于L2中的结点p2
            free(p2);//释放结点p2
            p2 = tem -> next;//将p2指向tem的下一个结点
            pc = pc -> next;
        }
    }
    
    if (p1 == NULL) { //p1遍历完毕了
        pc -> next = p2;
    }
    if (p2 == NULL) {//p2遍历完毕了
        pc -> next = p1;
    }
    free(*L2);//释放第二个数组的头结点
    return 1;
}

int main(int argc, const char * argv[]) {
    // insert code here...
    printf("Hello, World!\n");
    
    LinkList L1;
    LinkList L2;
    createList(&L1);
    traverse(L1);
    createList(&L2);
    traverse(L2);
    
    LinkList LC;
    combine(&L1, &L2, &LC);
    traverse(LC);
    return 0;
}

题目:
已知两个链表A和B分别表示两个集合.其元素递增排列. 设计一个算法,用于求出A与B的交集,并存储在A链表中;
例如:
La = {2,4,6,8}; Lb = {4,6,8,10};
Lc = {4,6,8}

关键词:依次摘取2个表中相等的元素重新进行链接,删除其他不等的元素;

int Intersection(LinkList* LA, LinkList* LB, LinkList* LC){
    LinkList p1 = *LA;
    LinkList p2 = *LB;
    LinkList temp;
    p1 = p1->next;//从LA链的首元结点开始
    p2 = p2->next;//从LB链的首元结点开始
    LinkList p3;
    *LC = *LA;//*LC是指向LA链的头结点
    p3 = *LC;//临时变量p3是用来记录*LC的结点跳跃
    while (p1 && p2) {
        if (p1 -> data == p2 -> data) {//如果相等
            p3 -> next = p1;
            temp = p2;
            p2 = temp -> next;
            free(temp);
            p1 = p1 ->next;
            p3 = p3 -> next;
        }else if (p1 -> data < p2 -> data){//如果第一个链结点小于第二个链的结点
            temp = p1;
            p1  = temp -> next;
            free(temp);
        }else{//若果第二个链结点小于第一个链结点
            temp = p2;
            p2 = temp -> next;
            free(temp);

        }
    }
    while (p1) { //去除LA其他结点
        temp = p1;
        p1 = temp -> next;
        free(temp);
        
    }
    
    while (p2) {//去除LB其他结点
        temp = p2;
        p2 = temp -> next;
        free(temp);
    }
    
    p3 -> next = NULL;
    free(*LB);
    return 1;
}

题目:
设计一个算法,将链表中所有节点的链接方向"原地旋转",即要求仅仅利用原表的存储空间. 换句话说,要求算法空间复杂度为O(1);
例如:L={0,2,4,6,8,10}, 逆转后: L = {10,8,6,4,2,0};

关键词:不能开辟新的空间,只能改变指针的指向; 可以考虑逐个摘取结点,利用前插法创建链表的思想,将结点一次插入到头结点的后面; 因为先插入的结点为表尾,后插入的结点为表头,即可实现链表的逆转;

//仔细看这个循环比如对于一个链表{1, 2, 3, 4, 5}
//第一次遍历的时候 p = 1, tem保存的是结点2
// 调用 p -> next = (*L) -> next 即 p -> next = NULL,
//调用 (*L) -> next = p; 即 *L -> next 指向 1结点
//第二次遍历 p = 2, tem 保存结点3
// 调用 p -> next = (*L) -> next 即 p -> next 指向于 1这个结点,
//调用 (*L) -> next = p; 即 *L -> next 指向 2结点 依次往复循环

int InverseList(LinkList* L){
    LinkList p = (*L) -> next;
    LinkList tem;
    (*L) -> next = NULL;
    while (p) {
        tem = p -> next;//tem指针保存的p->next
        
        p -> next = (*L) -> next;//间隔p->next指向与头结点的next 即 这个结点p 他要进入首元结点的位置,首先要做的就是 p->next与*L->next链接起来

        (*L) -> next = p;//将头结点的的next指向这个拿到的结点,即该节点成为首元结点

        p = tem;//开始处理最早保存好的下一个结点

    }
    return 1;
}

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

关键词: 通过遍历链表能够定位带删除元素的下边界和上边界, 即可找到第一个值大于mink的结点和第一个值大于等于maxk的结点;

int deleteMinMax(LinkList* L, int min, int max){
    LinkList p, q, pre;
    p = (*L) -> next;
    q = (*L) -> next;
    pre = *L;
    
    //1.查找第一值大于mink的结点
    while (p) {
        if (p -> data < min){
            pre = p;
            p = p->next;
        }else{
            break;
        }
    }
    
    //2.查找第一个值大于等于maxk的结点
    while (q) {
        if (q -> data <= max) {
            q = q -> next;
        }else{
            break;
        }
    }
    
    //3.修改待删除的结点指针

    p = pre -> next;
    pre -> next = q;
    
    while (p != q) {//释放被删除的结点
        LinkList temp = p;
        p = p -> next;
        free(temp);
    }
    
    return 1;
}

设将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. 先将n个数据原地逆置 9,8,7,6,5,4,3,2,1,0;
  2. 将n个数据拆解成[9,8,7,6,5,4,3] [2,1,0]
  3. 将前n-p个数据和后p个数据分别原地逆置; [3,4,5,6,7,8,9] [0,1,2]
int reverseArray(int* array,int left,int right){
    int i = left, j = right;
    int temp;
    while (i < j) {
        temp = array[i];
        array[i] = array[j];
        array[j] = temp;
        i++;
        j--;
    }
    return 1;
}

void leftShift(int* array, int length, int p){
    if (p < length && p > 0) {
        reverseArray(array, 0, length - 1);
        reverseArray(array, 0, length - p - 1);
        reverseArray(array, length - p, length);
    }
}

题目:
已知一个整数序列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的主元素. 例如,A = (0,5,5,3,5,7,5,5),则5是主元素; 若B = (0,5,5,3,5,1,5,7),则A 中没有主元素,假设A中的n个元素保存在一个一维数组中,请设计一个尽可能高效的算法,找出数组元素中的主元素,若存在主元素则输出该元素,否则输出-1.

题目分析:
主元素,是数组中的出现次数超过一半的元素; 当数组中存在主元素时,所有非主元素的个数和必少于一半. 如果让主元素和一个非主元素配对, 则最后多出来的元素(没有元素与之匹配就是主元素.

算法思路:

  1. 选取候选主元素, 从前向后依次扫描数组中的每个整数, 假定第一个整数为主元素,将其保存在Key中,计数为1. 若遇到下一个整数仍然等于key,则计数加1. 否则计数减1. 当计数减到0时, 将遇到的下一个整数保存到key中, 计数重新记为1. 开始新一轮计数. 即可从当前位置开始重上述过程,直到将全部数组元素扫描一遍;
  2. 判断key中的元素是否是真正的主元素, 再次扫描数组, 统计key中元素出现的次数,若大于n/2,则为主元素,否则,序列中不存在主元素;

算法分析:
时间复杂度: O(n)
空间复杂度: O(1)

int mainElement(int* array, int length){
//    int* tempArray = {0};
   int mainElement = array[0];
   int count = 1;
   for (int i = 1; i < length; i++) {//因为默认count = 1, 所以从第二位开始比较
       if (array[i] == mainElement) {
           count ++;
       }else{
           if (count > 0) {
               count --; //不想等,减一
           }else{
               mainElement = array[i];//更换新的主元素
               count = 1;//更换主元素默认为count = 1,从下一位开始比较
           }
       }
   }
//    int count = 0;
   if (count > 0) {
       count = 0;
       for (int i = 0; i < length; i++) {
           if (mainElement == array[i]) {//开始计算数量
               count++;
           }
       }
   }

   if (count > length/2) {//判断是否满足条件
       return mainElement;
   }
   return -1;
}

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

题目分析:
要求设计一个时间复杂度尽量高效的算法,而已知|data|<=n, 所以可以考虑用空间换时间的方法. 申请一个空间大小为n+1(0号单元不使用)的辅助数组. 保存链表中已出现的数值,通过对链表进行一趟扫描来完成删除.
算法思路:

  1. 申请大小为n+1的辅助数组t并赋值初值为0;
  2. 从首元结点开始遍历链表,依次检查t[|data|]的值, 若[|data|]为0,即结点首次出现,则保留该结点,并置t[|data|] = 1,若t[|data|]不为0,则将该结点从链表中删除.

复杂度分析:
时间复杂度: O(m),对长度为m的链表进行一趟遍历,则算法时间复杂度为O(m);
空间复杂度: O(n)

int DeleteEqualNode(LinkList* L, int temp_array_length){
    int* temp_array = malloc(sizeof(int) * temp_array_length);
    for (int i = 0; i < temp_array_length; i++) {
        *(temp_array + i) = 0;
    }
    
    LinkList p = (*L) -> next;
    LinkList tem = p;
    while (p) {
        if (temp_array[abs(p->data)] == 1) {//绝对值序号的值为1,证明已经来过了
            tem -> next = p -> next; // 直接将当前结点temp的next 指向 他的下一个结点p的next
            free(p);
            p = tem -> next;//继续记录当前结点的next,即p
        }else{
            temp_array[abs(p->data)] = 1;//第一次进来对数组赋值1
            tem = p; //第一次进来,记录当前结点tem
            p = tem -> next;//记录当前结点的next,即p
        }

    }
    
    return 1;
}

上一篇下一篇

猜你喜欢

热点阅读