利用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;
}
莫听穿林打叶声,何妨吟啸且徐行。
—— 宋 · 苏轼《 定风波·莫听穿林打叶声 》