数据结构与算法 - 链表实践

本文首发于 个人博客

链表只是一种数据结构,如果要通过数据结构来解决问题那就是算法了,所以这篇文章我们看看如何利用链表的数据结构去解决一些问题。 以下的代码均可 前往下载

合并有序链表

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

其实题目理解起来很简单 ,如果 La = {1,2,3,,6,9} ,Lb = {2,3,4,5,7,10} 那么合并后应该是Lc ={1,2,3,4,6,7,9,10},其规定不另外占用其他存储空间,那么我们就只能使用a,b这两个存储空间做手脚。

用 pa 和 pb 分别指向 La 和 Lb ,从首元节点开始比较,让Lc指向其中较小的值,并让较小的值依次后移,直到其中一个链表循环完毕,那么如果还有多余的数据,一定是比较大的,直接接在Lc后面即可:

Status MergeList(LinkList *La,LinkList *Lb,LinkList *Lc) {
    // 找到首元节点,依次遍历
    Node *pa = (*La)->next;
    Node *pb = (*Lb)->next;
    // 由于不能开辟新的空间,我们借用La的空间
    Node *pc = *La;
    // pc是一个局部变量 保存的是Lc的尾节点,每次都指向两个值中的最小值,如果相等则保持其一,删除释放另外一个
    while (pa && pb) {
        if (pa->data < pb->data) {
            pc->next = pa;
            pc = pa;// 这地方相当于pc = pc->next 也就是说pc指针也要后移
            pa = pa->next;
        } else if (pa->data > pb->data) {
            pc->next = pb;
            pc = pb;
            pb = pb->next;
        } else {
            Node *temp = pb;
            pc->next = pa;
            pc = pa;
            pa = pa->next;
            pb = pb->next;
            free(temp);
        }
    }

    // 最后多余的数据一定是后续最大的
    pc->next = pa?pa:pb;
    *Lc = *La;
    return SUCCESS;
}

验证

int main(int argc, const char * argv[]) {
    // insert code here...
    printf("Hello, World!\n");
    // 算法题1
    LinkList la,lb,lc;
    InitList(&la);
    InitList(&lb);
    InitList(&lc);
    for (int i = 6; i > 0; i --) {
        ListInsert(&la, 1, i);
    }
    printf("打印la :\n");
    ListTraverse(la);

    for (int i = 10; i > 2; i-=2) {
        ListInsert(&lb, 1, i);
    }
    printf("打印lb :\n");
    ListTraverse(lb);

    MergeList(&la, &lb, &lc);
    printf("打印lc :\n");
    ListTraverse(lc);
    return 0;
}
-----------------------------打印结果
Hello, World!
打印la :
1  2  3  4  5  6  
打印lb :
4  6  8  10  
打印lc :
1  2  3  4  5  6  8  10  
Program ended with exit code: 0

求交集

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

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

其实思路跟上面那道题差不多,也是用两个指针从前往后依次遍历,具体看代码,内部有详细注释

Status Intersection(LinkList *La,LinkList *Lb,LinkList *Lc) {
    Node *pa = (*La)->next;
    Node *pb = (*Lb)->next;
    Node *pc = *La;
    Node *temp;
    while (pa && pb) {
        // 每次碰到小的就过滤掉并释放
        if (pa->data < pb->data) {
            temp = pa;
            pa = pa->next;
            free(temp);
        } else if (pa->data > pb->data) {
            temp = pb;
            pb = pb->next;
            free(temp);
        } else {
            // 碰到相等的保留其中一个并释放另外一个
            pc->next = pa;
            pc = pc->next;
            pa = pa->next;
            temp = pb;
            pb = pb->next;
            free(temp);
        }
    }
    // 要额外判断多余的情况
    while (pa) {
        temp = pa;
        pa = pa->next;
        free(temp);
    }

    while (pb) {
        temp = pb;
        pb = pb->next;
        free(temp);
    }
    *Lc = *La;
    return SUCCESS;
}

验证

int main(int argc, const char * argv[]) {
    LinkList la,lb,lc;
    InitList(&la);
    InitList(&lb);
    InitList(&lc);
    for (int i = 6; i > 0; i --) {
        ListInsert(&la, 1, i);
    }
    printf("打印la :\n");
    ListTraverse(la);

    for (int i = 10; i > 2; i-=2) {
        ListInsert(&lb, 1, i);
    }
    printf("打印lb :\n");
    ListTraverse(lb);

    Intersection(&la, &lb, &lc);
    printf("打印lc :\n");
    ListTraverse(lc);
    return 0;
}
--------------------------- 打印结果
打印la :
1  2  3  4  5  6  
打印lb :
4  6  8  10  
打印 lc :
4  6  

链表倒序

设计一个算法,将链表中所有节点的链接方向"原地旋转",即要求仅仅利用原表的存储空间. 换句话说,要求算法空间复杂度为O(1);

这里的空间复杂度 O(1) 其实也是一样,不允许新的辅助空间,所以我们还是要在原有的链表基础上做手脚。

之前的文章中我们讲过链表的初始化有两种方式:头插法尾插法 ,最后我们发现尾插法跟我们日常逻辑差不多, 而头插法却是倒序的:因为先插入的结点为表尾,后插入的结点为表头,即可实现链表的逆转;:

void Inverse(LinkList *L) {
    //  头插法每次要插入的节点,初始是首元节点
    Node *target = (*L)->next;
    Node *tNext;
    // 为了让链表的尾结点指向NULL
    (*L)->next = NULL;
    while (target) {
        // 每次让tNext保存target之后的数据
        tNext = target->next;
        target->next = (*L)->next;
        (*L)->next = target;
        target = tNext;
    }
}

验证

int main(int argc, const char * argv[]) {
    LinkList la,lb,lc;
    InitList(&la);
    InitList(&lb);
    InitList(&lc);
    for (int i = 6; i > 0; i --) {
        ListInsert(&la, 1, i);
    }
    printf("打印la :\n");
    ListTraverse(la);

    Inverse(&la);
    printf("打印la :\n");
    ListTraverse(la);
    return 0;
}
------------------------------------打印结果
打印la :
1  2  3  4  5  6  
打印la :
6  5  4  3  2  1  
Program ended with exit code: 0

删除指定范围的节点

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

其实思路很简单,找到左边最后一个小于mink的节点,以及找到右边第一个大于maxk的节点。

void DeleteDataRange(LinkList *L, int min,int max) {
    Node *p = (*L)->next;
    Node *left = *L,*right=NULL;
    // 找到左边节点
    while (p && p->data < min) {
        left = p;
        p = p->next;
    }

    // 找到右边节点
    while (p && p->data <= max) {
        right = p;
        p = p->next;
    }
    right = right->next;

    // 左边节点指向右边节点
    left->next = right;

    // 临时保存要删除的节点进行释放
    Node *temp = left->next;
    while (temp && temp != right) {
        Node *del = temp;
        temp = temp->next;
        free(del);
    }
}

验证

int main(int argc, const char * argv[]) {
    LinkList la,lb,lc;
    InitList(&la);
    InitList(&lb);
    InitList(&lc);
    for (int i = 6; i > 0; i --) {
        ListInsert(&la, 1, i);
    }
    printf("打印la :\n");
    ListTraverse(la);

    DeleteDataRange(&la, 2, 3);
    printf("打印la :\n");
    ListTraverse(la);
    return 0;
}
---------------------打印结果
打印la :
1  2  3  4  5  6  
打印la :
1  4  5  6  

调整次序

设将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}

数组循环往左移动多少位,左边位置固定,超出的移到数组后面。注意:有的同学可能会想我用两个指针指向链表的两个位置进行重新排列就好了啊,这里题目中要求是数组而不是链表,所以不能用链表的方法去解决问题。

// 将数组指定范围的数据进行倒序
void Reverse (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左移
        i++;
        j--;
    }
}
//将长度为n的数组pre 中的数据循环左移p个位置
void LeftShift(int *pre,int n,int p){
    // 比如 {1,2,3,4,5} 向左移2位
    if (p > 0 && p < n) {
        // 整个数组数据全部倒序 {5,4,3,2,1}
        Reverse(pre, 0, n-1);
        // 前部分倒序 {3,4,5,2,1}
        Reverse(pre, 0, n-p-1);
        // 后部分倒序 {3,4,5,1,2}
        Reverse(pre, n-p, n-1);
    }
}

其实就是反复利用倒序的函数对数组进行调整,这里就不做验证了,请自行去验证一下即可。

找元素

已知一个整数序列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。题目中提示尽可能高效,意思就是优先满足时间复杂度,所以我们可以考虑用空间换时间。

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

int MainElement(int *A, int n){

    //目标: 求整数序列A中的主元素;
    //count 用来计数
    int count = 1;
    //key 用来保存候选主元素, 初始A[0]
    int key = A[0];

    //(1) 扫描数组,选取候选主元素
    for (int i = 1; i < n; i++) {
        //(2) 如果A[i]元素值 == key ,则候选主元素计数加1;
        if (A[i] == key) {
            count++;
        }else{
            //(3) 当前元素A[i] 非候选主元素,计数减1;
            if(count >0){
                count--;

            }else{
               //(4) 如果count 等于0,则更换候选主元素,重新计数
                key = A[i];
                count = 1;
            }

        }
    }

    //如果count >0
    if (count >0){
        //(5)统计候选主元素的实际出现次数
        for (int i = count = 0; i < n; i++)
            if (A[i] == key) count++;
    }

    //(6)判断count>n/2, 确认key是不是主元素
    if (count > n/2) return key;
    else return -1; //不存在主元素

}

链表去重

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

题目分析:

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

void DeleteEqualNode(LinkList *L,int n) {
    int *p = malloc(sizeof(int)*n);
    for (int i = 0; i < n; i ++) {
        *(p+i) = 0;
    }
    // r 指向头节点
    Node *r = *L;
    // 首元节点
    Node *temp = (*L)->next;
    while (temp) {
        ListData absData = abs(temp->data);
        if (p[absData] == 1) {// 说明已经有值了
            r->next = temp->next;
            free(temp);
        } else {
            p[absData] = 1;
            r = temp;
        }
        temp = temp->next;
    }
}

其实相当于把数字的绝对值作为数组下标保存起来,内部的值只有0和1,这样就可以遍历的过程中直接取值进行比较,速度会非常快,无非就是初始化的时候需要一个大小为n的数组空间

总结

以上就是最近学习的链表相关的知识运用以及相关的问题解答,后续有相关的题目还会陆续更新。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,165评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,503评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,295评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,589评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,439评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,342评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,749评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,397评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,700评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,740评论 2 313
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,523评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,364评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,755评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,024评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,297评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,721评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,918评论 2 336

推荐阅读更多精彩内容