链表
说到链表,我们可以先回顾一下数组。数组,所有元素都连续的存储于一段内存中,且每个元素占用的内存大小相同。这使得数组具备了通过下标快速访问数据的能力。但连续存储的缺点也很明显,增加容量,增删元素的成本很高,时间复杂度均为 O(n)。增加数组容量需要先申请一块新的内存,然后复制原有的元素。如果需要的话,可能还要删除原先的内存。删除元素时需要移动被删除元素之后的所有元素以保证所有元素是连续的。增加元素时需要移动指定位置及之后的所有元素,然后将新增元素插入到指定位置,如果容量不足的话还需要先进行扩容操作。
链表链表是一系列的存储数据元素的单元通过指针串接起来形成的,因此每个单元至少有两个域,一个域用来存放数据的,另一个域或者两个域用于指向其他单元的指针。这里具有一个数据域和多个指针域的存储单元通常称为节点(node)。链表的第一个结点和最后一个结点,分别称为链表的头结点和尾结点。
链表结构
链表数据结构中主要分为单链表和双链表
1、不带头的单链表 (常考&常用)
2、带头的单链表
3、循环单链表
4、双向链表
5、双向循环链表
6、带头双向循环链表(常用)
各种链表的优缺点
链表的操作
单链表操作
这里我们主要对无头单链表进行一些基本的增删查改的操作
操作接口
#pragma once
#include<stdio.h>
#include<stdlib.h>
typedef int LDataType;
typedef struct listNode
{
LDataType data;
struct listNode* next;
}listNode;
typedef struct list
{
struct listNode* head;
}list;
//创建节点
struct listNode* creatListNode(LDataType val);
//初始化链表
void listInit(struct list* lst);
//尾插
void listPushBack(struct list* lst, LDataType val);
//尾删
void listPopBack(struct list* lst);
//前插
void listPushFront(struct list* lst, LDataType val);
//前删
void listPopFront(struct list* lst);
//在当前节点的写一个插入
void listInsertAfter(struct listNode* node, LDataType val);
//删除当前节点的下一个
void listEraseAfter(struct listNode* node);
//查找节点
listNode* listFind(struct list* lst, LDataType val);
//摧毁链表
void listDestroy(struct list* lst);
//删除当前节点
void listErase(struct listNode* node);
实现接口操作
#include"list.h"
struct listNode* creatListNode(LDataType val)
{
struct listNode* node = (struct listNode*)malloc(sizeof(listNode));
node->data = val;
node->next = NULL;
return node;
}
void listInit(struct list* lst)
{
if (lst == NULL)
return;
lst->head = NULL;
}
void listPushBack(struct list* lst, LDataType val)
{
if (lst == NULL)
return;
if (lst->head == NULL)
{
lst->head = creatListNode(val);
}
else
{
struct listNode* tail = lst->head;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = creatListNode(val);
}
}
void listPopBack(struct list* lst)
{
if (lst == NULL || lst->head == NULL)
return;
struct listNode* prev =NULL;
struct listNode* cur = lst->head;
while (cur->next!= NULL)
{
prev = cur;
cur = cur->next;
}
free(cur);
if (prev == NULL)
{
lst->head = NULL;
}
}
void listPushFront(struct list* lst, LDataType val)
{
if (lst == NULL)
return;
struct listNode* node = creatListNode(val);
node->next = lst->head;
lst->head = node;
}
void listPopFront(struct list* lst)
{
if (lst == NULL || lst->head == NULL)
return;
struct listNode* temp = lst->head->next;
free(lst->head);
lst->head = temp;
}
void listInsertAfter(struct listNode* node, LDataType val)
{
if (node == NULL)
return;
struct listNode* newNode = creatListNode(val);
struct listNode* next = node->next;
node->next = newNode;
newNode->next = next;
}
void listEraseAfter(struct listNode* node)
{
if (node == NULL || node->next ==NULL)
return;
struct listNode* next = node->next;
struct listNode* nextnext = node->next->next;
free(next);
node->next = nextnext;
}
listNode* listFind(struct list* lst, LDataType val)
{
if (lst == NULL)
return NULL;
struct listNode* tail = lst->head;
while (tail != NULL)
{
if (tail->data == val)
return tail;
tail = tail->next;
}
return NULL;
}
void listDestroy(struct list* lst)
{
if (lst == NULL)
return;
struct listNode* prev = lst->head;
while (prev != NULL)
{
struct listNode* tail = prev->next;
free(prev);
prev = tail;
}
}
void listErase(struct listNode* node , struct list* lst)
{
if (lst->head == node)
{
listPopFront(lst);//头删
}
else
{
struct listNode* prev = lst->head; //前一个节点
struct listNode* cur = prev->next; //当前节点
struct listNode* next = node->next; //记录要被删除的节点的下一个节点
while (cur != node)
{
prev = prev->next;
cur = prev->next;
}
free(cur);
prev->next = next;
}
}
带头双循环链表操作
操作接口
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
LTDataType data;
}ListNode;
// 创建返回链表的头结点.
ListNode* ListCreate();
//创建一个带有数据的结点
ListNode* BuyListNode(LTDataType x);
// 双向链表销毁
void ListDestory(ListNode* phead);
// 双向链表打印
void ListPrint(ListNode* phead);
// 双向链表尾插
void ListPushBack(ListNode* phead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* phead);
// 双向链表头插
void ListPushFront(ListNode* phead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* phead);
// 双向链表查找
ListNode* ListFind(ListNode* phead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);
操作接口实现
#include"list.h"
// 创建返回链表的头结点.
ListNode* ListCreate()
{
ListNode* head = (ListNode*)malloc(sizeof(ListNode));
if(head)
head->next = head->prev = head;
return head;
}
//创建一个带有数据的结点
ListNode* BuyListNode(LTDataType x)
{
ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
if (newNode)
{
newNode->data = x;
newNode->next = NULL;
newNode->prev = NULL;
}
return newNode;
}
// 双向链表销毁
void ListDestory(ListNode* phead)
{
ListNode* cur = phead->next;
while (cur != phead)
{
ListNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
phead = NULL;
}
// 双向链表打印
void ListPrint(ListNode* phead)
{
ListNode* cur = phead->next;
while (cur != phead)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
// 双向链表尾插
void ListPushBack(ListNode* phead, LTDataType x)
{
ListNode* newNode = BuyListNode(x);
ListNode* tail = phead->prev;
tail->next = newNode;
newNode->prev = tail;
newNode->next = phead;
phead->prev = newNode;
}
// 双向链表尾删
void ListPopBack(ListNode* phead)
{
assert(phead->next != phead);
ListNode* tail = phead->prev;
ListNode* prev = tail->prev;
free(tail);
prev->next = phead;
phead->prev = prev;
}
// 双向链表头插
void ListPushFront(ListNode* phead, LTDataType x)
{
ListNode* newNode = BuyListNode(x);
ListNode* first = phead->next;
phead->next = newNode;
newNode->next = first;
newNode->prev = phead;
first->prev = newNode;
}
// 双向链表头删
void ListPopFront(ListNode* phead)
{
assert(phead->next != phead);
ListNode* first = phead->next;
ListNode* second = first->next;
free(first);
phead->next = second;
second->prev = phead;
}
// 双向链表查找
ListNode* ListFind(ListNode* phead, LTDataType x)
{
ListNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
return cur;
cur = cur->next;
}
return NULL;
}
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
ListNode* prev = pos->prev;
ListNode* newNode = BuyListNode(x);
prev->next = newNode;
newNode->prev = prev;
newNode->next = pos;
pos->prev = newNode;
}
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
ListNode* prev = pos->prev;
ListNode* next = pos->next;
free(pos);
next->prev = prev;
prev->next = next;
}
链表的应用举例
无法高效获取长度,无法根据偏移快速访问元素,是链表的两个劣势。然而在笔试面试的时候经常碰见一些关于链表长度和位置的问题。如:反转链表。输出链表倒数第k个元素。判断链表是否有环。找出链表的公共结点等等。
1、删除链表中等于给定值 val 的所有节点
question1:删除链表中等于给定值 val 的所有节点 输入: 1->2->6->3->4->5->6, val = 6 输出: 1->2->3->4->5
struct ListNode* removeElements(struct ListNode* head, int val)
{
struct ListNode* prev = NULL;
struct ListNode* cur = head;
while(cur)
{
if(cur->val==val)
{
struct ListNode* next = cur->next;
free(cur);
if(prev == NULL)
head = next;
else
prev->next = next;
cur = next;
}
else
{
prev = cur;
cur = cur->next;
}
}
return head;
}
2、反转一个单链表
question2:反转一个单链表。输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL
struct ListNode* reverseList(struct ListNode* head)
{
struct ListNode* newHead,*cur;
cur = head;
newHead = NULL;
while(cur)
{
struct ListNode* next = cur->next;
cur->next = newHead;
newHead = cur;
cur = next;
}
return newHead;
}
3、查找链表的中间结点
question3:给定一个头结点为 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。输入:[1,2,3,4,5]
输出:此列表中的结点 3 (序列化形式:[3,4,5]) 输入:[1,2,3,4,5,6]
输出:此列表中的结点 4 (序列化形式:[4,5,6])
struct ListNode* middleNode(struct ListNode* head)
{
struct ListNode* slow = head;
struct ListNode* fast = head;
while(fast && fast->next)
{
fast = fast->next->next;
slow = slow ->next;
}
return slow;
}
4、返回链表中倒数第k个结点。
question4:输入一个链表,输出该链表中倒数第k个结点。输入1 {1,2,3,4,5} 返回值{5}
struct ListNode* FindKthToTail(struct ListNode* pListHead, int k )
{
/*struct ListNode* cur = pListHead;
int n = 0;
while(cur)
{
n++;
cur = cur->next;
}
if(k>n)
return NULL;
n = n-k;
cur = pListHead;
while(n--)
{
cur = cur->next;
}
return cur;*/
struct ListNode* fast, *slow;
fast = slow = pListHead;
while(k--)
{
if(fast)
fast = fast->next;
else
return NULL;
}
while(fast)
{
fast = fast->next;
slow = slow->next;
}
return slow;
}
5、判断一个链表是否有环
question5:给定一个链表,判断链表中是否有环。如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。
class Solution {
public:
bool hasCycle(ListNode *head) {
if(head==NULL|| head->next==NULL)
return false;
ListNode* fast = head;
ListNode* slow = head;
while(fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
if(fast == slow)
return true;
}
return false;
}
};
6、找出环形链表中第一个入环结点
question6:给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
if(head==NULL)
return NULL;
ListNode* fast = head;
ListNode* slow = head;
int flag = 0;
while(fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
if(fast == slow)
{
flag = 1;
break;
}
}
if(flag == 0)
{
return NULL;
}
fast = head;
while(fast!=slow)
{
fast = fast->next;
slow = slow->next;
}
return slow;
}
};
7、合并两个链表
question7:将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 输入:1->2->4, 1->3->4 输出:1->1->2->3->4->4
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2)
{
if(l1==NULL || l2==NULL)
return l1==NULL ? l2:l1;
struct ListNode* newHead, *cur1, *cur2, *tail;
cur1 = l1;
cur2 = l2;
if(l1->val<=l2->val)
{
newHead = l1;
cur1 = cur1->next;
}
else
{
newHead = l2;
cur2 = cur2->next;
}
tail = newHead;
while(cur1 && cur2)
{
if(cur1->val<=cur2->val)
{
tail->next = cur1;
cur1 = cur1->next;
tail = tail->next;
}
else
{
tail->next = cur2;
cur2 = cur2->next;
tail = tail->next;
}
}
if(cur1)
tail->next = cur1;
if(cur2)
tail->next = cur2;
return newHead;
}
};
8、链表分割
question8:现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针
class Partition {
public:
ListNode* partition(ListNode* pHead, int x)
{
if(pHead == NULL)
return pHead;
struct ListNode* s = (struct ListNode*)malloc(sizeof(ListNode));
struct ListNode* b = (struct ListNode*)malloc(sizeof(ListNode));
struct ListNode* cur = pHead;
struct ListNode* stail = s;
struct ListNode* btail = b;
while(cur)
{
struct ListNode* next = cur->next;
if(cur->val<x)
{
stail->next = cur;
stail = stail->next;
}
else
{
btail->next = cur;
btail = btail->next;
}
cur = next;
}
if(btail)
btail->next = NULL;
stail->next =把 b->next;
struct ListNode* newHead = s->next;
free(b);
free(s);
return newHead;
}
};
9、复制带随机指针的链表
question9:给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。要求返回这个链表的 深拷贝。输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
/*
// Definition for a Node.
class Node {
public:
int val;
Node* next;
Node* random;
Node(int _val) {
val = _val;
next = NULL;
random = NULL;
}
};
*/
class Solution {
public:
Node* copyRandomList(Node* head) {
if(head == NULL)
return head;
struct Node* cur = head;
//1、拷贝结点
while(cur)
{
struct Node* next = cur->next;
struct Node* node = (struct Node*)malloc(sizeof(Node));
node->val = cur->val;
cur->next = node;
node->next = next;
cur = next;
}
//2、拷贝随机指针
cur = head;
while(cur)
{
struct Node* node = cur->next;
if(cur->random)
node->random = cur->random->next;
else
node->random = NULL;
cur = node->next;
}
//3、拆链
cur = head;
struct Node* newHead = NULL;
while(cur)
{
struct Node* copy = cur->next;
struct Node* next = copy->next;
cur->next = next;
if(newHead == NULL)
newHead = copy;
if(next)
copy->next = next->next;
cur = next;
}
return newHead;
}
};
10、对链表进行插入排序
question10:插入排序的动画演示如下。从第一个元素开始,该链表可以被认为已经部分排序(用黑色表示)。
每次迭代时,从输入数据中移除一个元素(用红色表示),并原地将其插入到已排好序的链表中。
做这道题之前,我们先科普一下插入排序
1、插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
2、每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
3、重复直到所有输入数据插入完为止。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* insertionSortList(ListNode* head) {
if(head == NULL || head->next ==NULL)
return head;
struct ListNode* node = head->next;
struct ListNode* cur = head;
struct ListNode* tail = head;
while(node)
{
struct ListNode* next = node->next;
if(node->val > tail->val)
{
tail->next = node;
tail = node;
}
else
{
struct ListNode* prev = NULL;
while(node->val > cur->val)
{
prev = cur;
cur = cur->next;
}
if(prev == NULL)
{
node->next = head;
head = node;
}
else
{
prev->next = node;
node->next = cur;
}
node = next;
cur = head;
}
}
tail->next = NULL;
return head;
}
};
11、删除链表中重复的结点
question11:在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
*/
class Solution {
public:
ListNode* deleteDuplication(ListNode* pHead)
{
if(pHead == NULL ||pHead->next ==NULL)
return pHead;
struct ListNode* prev = NULL;
struct ListNode* cur = pHead;
struct ListNode* next = cur->next;
while(next)
{
if(cur->val == next->val)
{
while(next && next->val == cur->val)
{
next = next->next;
}
while(cur!=next)
{
struct ListNode* next = cur->next;
free(cur);
cur = next;
}
if(prev == NULL)
{
pHead = cur;
}
else
{
prev->next = cur;
}
if(next)
{
next = next->next;
}
}
else
{
prev = cur;
cur = next;
next = next->next;
}
}
return pHead;
}
};
看文本篇博客,相信对读者会对链表有一定的了解,而且在笔试面试中遇到相关的题也可以迎刃而解