C语言--常用的链表操作

利用C语言实现链表,并定义一些常用的操作

链表定义

使用typedef简化代码

#include <stdio.h>
#include <stdlib.h>

typedef struct LinkedList {
    int data;
    struct LinkedList *next;
} LinkedList;

新建一个链表结点

LinkedList *createLinkedList(int i) {
    LinkedList *list = (LinkedList *)malloc(sizeof(LinkedList));
    list->data = i;
    list->next = NULL;
    return list;
}

打印链表

void printAll(LinkedList *head) {
    LinkedList *current = head;
    while (current != NULL) {
        printf(" --->%d", current->data);
        current = current->next;
    }
    printf("\n");
}

插入结点

头插法(常用)

采用头插法产生的链表和输入的数字是逆序的。
例如:一次插入1,2,3,4,5。链表为:5–>4 -->3 -->2 -->1

LinkedList **p 是指向指针的指针,用于直接修改调用者作用域中的头指针。如果采用LinkedList *p那么只能修改指针中的数值,而不能直接修改头指针,那么头插法也就没法实现了。

void insertAtHead(LinkedList **p, int data) {
    LinkedList *newNode = malloc(sizeof(LinkedList));
    newNode->data = data;
    newNode->next = *p;
    *p = newNode;
}

运行

int main() {
    LinkedList *head2 = NULL;
    insertAtHead(&head2, 1);
    insertAtHead(&head2, 2);
    insertAtHead(&head2, 3);
    insertAtHead(&head2, 4);
    printAll(head2);
    
    return 0;
}

在这里插入图片描述

尾插法(使用较少)

遍历到最后一个结点,然后把目标结点挂到链表上

void insertAtTail(LinkedList **p, int data) {
    LinkedList *newNode = malloc(sizeof(LinkedList));
    newNode->data = data;
    newNode->next = NULL;

    if (*p == NULL) {
        *p = newNode;
    }else {
        LinkedList *current = *p;
        // 遍历到最后一个节点
        while (current->next != NULL) {
            current = current->next;
        }
        current->next = newNode;
    }
}

运行

与头插法顺序恰恰相反

int main() {
    LinkedList *head2 = NULL;
    insertAtTail(&head2, 1);
    insertAtTail(&head2, 2);
    insertAtTail(&head2, 3);
    insertAtTail(&head2, 4);
    printAll(head2);

    return 0;
}

在这里插入图片描述

返回链表长度

int lengthOfNode(LinkedList *head) {
    int length = 0;
    LinkedList *current = head;
    while (current != NULL) {
        length++;
        current = current->next;
    }
    return length;
}

链表转置

采用三指针法,每次循环保存prev和next,实现局部转置,直到全部结点都实现转置。此时current为NULL,prev刚好指向最后一个元素,也就是转置后的第一个元素,使其为头节点即可。

void reverseList(LinkedList **head) {
    LinkedList *current = *head;
    LinkedList *prev = NULL;
    LinkedList *next = NULL;
    while (current != NULL) {
       
        next = current->next; // 保存next
        current->next = prev; // 实现局部转置

        prev = current;
        current = next;
    }
    *head = prev;
}

运行

int main() {
    LinkedList *head2 = NULL;
    insertAtHead(&head2, 1);
    insertAtHead(&head2, 3);
    insertAtHead(&head2, 5);
    insertAtHead(&head2, 6);
    insertAtHead(&head2, 99);
    
    printAll(head2);
    reverseList(&head2);
    printAll(head2);

    return 0;
}

在这里插入图片描述

合并两个有序的链表

创建一个临时链表,当两个链表都不为空时,比较链表结点大小,总是将值小的链表结点挂到临时链表上,当有一个链表为空时,直接将剩下的那个链表结点全部挂到临时链表即可。

LinkedList *mergeList(LinkedList *a, LinkedList *b) {
    LinkedList *pHead = malloc(sizeof(LinkedList));
    pHead->data = -1;
    LinkedList *current = pHead;

    while (a!=NULL && b!=NULL) {
        LinkedList *tmp = malloc(sizeof(LinkedList));
        if (a->data < b->data) {
            tmp->data = a->data;
            current->next = tmp;

            current = current->next;
            a = a->next;
        }else {
            tmp->data = b->data;
            current->next = tmp;
            current = current->next;
            b = b->next;
        }
    }

    if (a!=NULL) {
        current->next = a;
    }

    if (b!=NULL) {
        current->next = b;
    }

    return pHead->next;
}

运行

int main() {

    LinkedList *head1 = NULL;
    insertAtTail(&head1, 1);
    insertAtTail(&head1, 3);
    insertAtTail(&head1, 8);
    printAll(head1);

    LinkedList *head2 = NULL;
    insertAtTail(&head2, 2);
    insertAtTail(&head2, 7);
    insertAtTail(&head2, 99);
    printAll(head2);

    printAll(mergeList(head1, head2));

    return 0;
}

在这里插入图片描述

删除最小结点

先创建一个值为-1的哑结点,为了简化对链表为NULL时的处理。
通过循环找到最小结点,并且记录最小结点的前结点。前结点下一个结点指向最小结点的下一个结点即可(跨过最小结点)

void deleteMinNode(LinkedList **head) {
     LinkedList *tmpHead = createLinkedList(-1);
     tmpHead->next = *head;

    LinkedList *cur = tmpHead->next;
    LinkedList *prev = tmpHead;

     LinkedList *prevMin = tmpHead;
     LinkedList *minNode = cur;
     while (cur != NULL) {
         if (cur->data < minNode->data) {
           prevMin = prev;
           minNode = cur;
         }
         prev = cur;
         cur = cur->next;
     }

     prevMin->next = minNode->next;
     *head = tmpHead->next;
}

运行

int main() {
    LinkedList *head1 = NULL;
    insertAtTail(&head1, 1);
    insertAtTail(&head1, 3);
    insertAtTail(&head1, 8);
    insertAtTail(&head1, 99);
    printAll(head1);
    deleteMinNode(&head1);
    printAll(head1);
    
    return 0;
}

在这里插入图片描述

int main() {

    LinkedList *head1 = NULL;
    insertAtTail(&head1, 88);
    insertAtTail(&head1, 3);
    insertAtTail(&head1, 8);
    insertAtTail(&head1, 99);
    printAll(head1);
    deleteMinNode(&head1);
    printAll(head1);

    return 0;
}

在这里插入图片描述

打印倒数第k个元素

利用快慢指针法,快指针先走k步;然后循环,快指针走到尾,慢指针刚好指向倒数第k个结点

void printLastKNode(LinkedList *head, int k) {
    LinkedList *current = head;
    LinkedList *slow = head;
    LinkedList *fast = NULL;
    while (k-->0) {
        fast = current->next;
        current = current->next;
    }
    while (fast != NULL) {
        slow = slow->next;
        fast = fast->next;
    }
    printf("--->%d", slow->data);
}

运行

int main() {

    LinkedList *head1 = NULL;
    insertAtTail(&head1, 88);
    insertAtTail(&head1, 3);
    insertAtTail(&head1, 8);
    insertAtTail(&head1, 99);

    printAll(head1);
    printLastKNode(head1, 1);
    printf("\n");
    printLastKNode(head1, 2);
    printf("\n");
    printLastKNode(head1, 4);

    return 0;
}

在这里插入图片描述

循环右移k个位置

将循环右移k个位置,例如k=2, 123456变为:561234
先找到分割结点的前一个结点,此时将链表断为两段,即:1–2–3–4–NULL 和 5–6–NULL
然后将后半段next指向前半段即可。

void rightCycleKNode(LinkedList **head, int k) {
    int length = 1;
    LinkedList *backNode = *head;
    while (backNode->next != NULL) {
        length++;
        backNode = backNode->next;
    }
    // now back is point at tail: 5--6--NULL
    int preK = length - k - 1;
    LinkedList *front = *head;
    LinkedList *newHead = NULL;
    // 找到分割结点的前一个结点,此时也可以快慢指针,先让快指针先走k步,然后快指针指向尾结点时,慢指针刚好指向分割结点的前一个结点
    while (preK-- > 0) {
        front = front->next;
    }
    // after loop, now node is point at 4

    newHead = front->next;  // 此时new: 5--6--NULL
    front->next = NULL; // cut front line: 1--2--3--4--NULL(head此时和front一样,只是指向的结点不一样)
    backNode->next = *head;  // 把1234接到6的后边,6--1--2

    *head = newHead;
}

运行

int main() {

    LinkedList *head1 = NULL;
    insertAtTail(&head1, 1);
    insertAtTail(&head1, 2);
    insertAtTail(&head1, 3);
    insertAtTail(&head1, 4);
    insertAtTail(&head1, 5);
    insertAtTail(&head1, 6);

    printAll(head1);
    rightCycleKNode(&head1, 2);
    printAll(head1);

    return 0;
}

在这里插入图片描述

判断链表是否有环

采用快慢指针法,快指针每次走两步,慢指针每次走一步,如果存在环,快指针最终会沿着环与慢指针相遇(类似于跑步中的套圈)

void hasCycle(LinkedList *head) {
    LinkedList *fast = head;
    LinkedList *slow = head;
    while (fast->next != NULL) {
        slow = slow->next;
        fast = fast->next->next;
        if (slow == fast) {
            printf("This LinkedList have cycle!\n");
            return;
        }
    }
    printf("This LinkedList doesn't has cycle!\n");
}

运行

int main() {

    LinkedList *head1 = NULL;
    insertAtTail(&head1, 1);
    insertAtTail(&head1, 2);
    insertAtTail(&head1, 3);

    hasCycle(head1);
    head1->next->next->next = head1;
    hasCycle(head1);

    return 0;
}

在这里插入图片描述


莫听穿林打叶声,何妨吟啸且徐行。

—— 宋 · 苏轼《 定风波·莫听穿林打叶声 》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小雅痞

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

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

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

打赏作者

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

抵扣说明:

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

余额充值