链表的实现:
SingleList.c 文件
#include "SingleList.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void singleListInit(Singlelist* sl) {
sl->_head = NULL;
}
Node* creatNode(Type data) {
Node* node = (Node*)malloc(sizeof(Node));
node->_data = data;
node->_next = NULL;
return node;
}
//尾插操作
//思路: 找到最后一个元素,然后将新创造的节点连接在最后的一位即可.
void singleListPushBack(Singlelist* sl, Type data) {
Node* node = creatNode(data);
if (sl->_head == NULL) {
sl->_head = node;
}
else {
Node* last = sl->_head;
//找到最后一个节点
while (last->_next) {
last = last->_next;
}
//执行尾插操作
last->_next = node;
}
}
//尾删操作:
//思路:找到最后一个节点删除,并且修改前驱的指向为NULl;
void singleListPopBack(Singlelist* sl) {
//找到最后一个节点,并且修改被删除节点的前驱节点的指向
if (sl->_head) {
//找到最后一个节点,遍历的过程更新prev
Node* prev = NULL;
Node* Tail = sl->_head;
while (Tail->_next) {
prev = Tail;
Tail = Tail->_next;
}
if (Tail == sl->_head) {
sl->_head = NULL;
}
else {
prev->_next = NULL;
free(Tail);
}
}
}
//头插操作
//思路: 直接修改头部的指向并且插入新的节点
void singleListPushFront(Singlelist* sl, Type data) {
Node* node = creatNode(data); //创造新的节点
if (sl->_head == NULL) {
sl->_head = node; //假如头部为空的话,直接将新的节点放到给头部
}
else {
node->_next = sl->_head; //修改指向
sl->_head = node;
}
}
//头删操作
//思路:
//1.先将原来要释放的元素保留下来2.将下一个节点给头部3.然后在释放保留之前的节点
void singleListPopFront(Singlelist* sl) {
if (sl->_head == NULL) {
return NULL;
}
else {
if (sl->_head) {
Node* cur = sl->_head; // 保留头结点
sl->_head = cur->_next; //将头结点的指向移到头结点的位置
free(cur); //释放之前保留的头结点
}
}
}
//给pos 后面的节点插入元素
//思路:1.pos位置 2.pos的下一个位置 next 3. 新的节点 newNode
void singleListInsertAfter(Node* pos, Type data) {
if (pos == NULL) {
return;
}
else {
Node* newNode = creatNode(data);
//必须要了解 1.pos位置 2.pos的下一个位置 next 3. 新的节点 newNode
Node* next = pos->_next;
newNode->_next = next;
pos->_next = newNode;
}
}
// 删除pos位置后面的节点
//思路: 要有pos next next->_next
void singleListEraseAfter(Node* pos) {
if (pos == NULL) {
return;
}
else {
Node* next = pos->_next;
if (next) {
pos->_next = next->_next;
free(next);
}
}
}
Node* find(Singlelist* sl, Type data) {
Node* cur = sl->_head;
while (cur) {
if (cur->_data == data) {
return cur;
}
cur = cur->_next;
}
return NULL;
}
void singleListPrint(Singlelist* sl) {
Node* cur = sl->_head;
while (cur) {
printf("%d ", cur->_data);
cur = cur->_next;
}
printf("\n");
}
Singlelist.h文件
//不带头单向不循环链表
#pragma once
typedef int Type;
typedef struct Node{
Type _data;
struct Node* _next;
}Node;
typedef struct Singlelist{
Node* _head;
}Singlelist;
Node* creatNode(Type data);
void singleListInit(Singlelist* sl);
void singleListPushBack(Singlelist* sl, Type data);
void singleListPopBack(Singlelist* sl);
void singleListPushFront(Singlelist* sl, Type data);
void singleListPopFront(Singlelist* sl);
void singleListInsertAfter(Node* pos, Type data);
void singleListEraseAfter(Node* pos);
void singleListPrint(Singlelist* sl);
Node* find(Singlelist* sl, Type date);
//test.c 测试文件\
#include "SingleList.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void test1() {
Singlelist sl;
singleListInit(&sl);
singleListPushBack(&sl, 1);
singleListPushBack(&sl, 2);
singleListPushBack(&sl, 3);
singleListPushBack(&sl, 4);
singleListPushBack(&sl, 5);
singleListPushBack(&sl, 6);
singleListPrint(&sl);
singleListPopBack(&sl);
singleListPrint(&sl);
singleListPopBack(&sl);
singleListPrint(&sl);
singleListPopBack(&sl);
singleListPrint(&sl);
singleListPopBack(&sl);
singleListPrint(&sl);
singleListPopBack(&sl);
singleListPrint(&sl);
singleListPopBack(&sl);
singleListPrint(&sl);
}
void test2() {
Singlelist sl;
singleListInit(&sl);
singleListPushFront(&sl, 1);
singleListPushFront(&sl, 2);
singleListPushFront(&sl, 3);
singleListPushFront(&sl, 4);
singleListPushFront(&sl, 5);
singleListPushFront(&sl, 6);
singleListPrint(&sl);
singleListPopFront(&sl);
singleListPrint(&sl);
singleListPopFront(&sl);
singleListPrint(&sl);
singleListPopFront(&sl);
singleListPrint(&sl);
}
void test3() {
Singlelist sl;
singleListInit(&sl);
singleListPushFront(&sl, 1);
singleListPushFront(&sl, 2);
singleListPushFront(&sl, 3);
singleListPushFront(&sl, 4);
singleListPushFront(&sl, 5);
singleListPushFront(&sl, 6);
singleListPrint(&sl);
Node* pos = find(&sl, 5);
printf("找到元素5,给5的后面插入100: ");
singleListInsertAfter(pos, 100);
singleListPrint(&sl);
Node* pos1 = find(&sl, 1); //找到 1 这个元素,在1 的后面插入200
printf("找到元素1,给1的后面插入200: ");
singleListInsertAfter(pos1, 200);
singleListPrint(&sl);
Node* pos3 = find(&sl, 1);
printf("找到元素1,删除1后面的元素: ");
singleListEraseAfter(pos3);
singleListPrint(&sl);
Node* pos4 = find(&sl, 5);
printf("找到元素5,删除5后面的元素: ");
singleListEraseAfter(pos4);
singleListPrint(&sl);
}
int main () {
//test1();
//test2();
test3();
system("color A");
system ("pause");
return 0;
}
test1函数实现尾插和尾删的操作
test2函数实现头插头删操作
test3函数实现在pos之后位置的插入和删除
重点习题的整理:
1.删除表中等于给定值val的所有节点.
基本思想: 依次进行遍历链表看对应的值是否相等即可
特殊情况处理: 假如删除的是头节点重新更新头部
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
//定义要删除的节点
ListNode* cur = head;
//前驱节点: 作用是假如当前节点的值等于val, 那么进行删除节点, 此时prev需要重新进行连接起来(prev->next = cur->next))
ListNode* prev = nullptr;
while (cur) {
if (cur->val == val) {
ListNode*next = cur->next;
//
//判断删除的节点是不是头结点
if (prev == nullptr) //如果要删除的节点是头节点, 那么需要重新更新头部信息
head = next;
else
prev->next = next; //如果要删除的节点不是头节点, 要重新连接指向
///
delete cur;//删除当前相等的节点
cur = next;//向后面移动
}
else {
//如果删除节点和头结点根本不相等, 那么重新进行移动元素即可
prev = cur;
cur = cur->next;
}
}
return head; //因为链表是连接起来的, 因此返回头部就可以
}
};
2.反转链表(头插法实现)
基本思想:
节点进行定义; 依次改变节点的定义与节点的更新
class Solution {
public:
ListNode* reverseList(ListNode* head) {
struct ListNode* newHead = NULL;
struct ListNode* cur = head;
while (cur) {
//将本身的next先找到, 然后再进行后面的更新
struct ListNode* next = cur->next;
cur->next = newHead;
newHead = cur;
cur = next;
}
return newHead;
}
};
3.给定一个带头节点head的非空单链表,返回链表的中间节点,如果有两个中间结点,则返回第二个中间结点.
基本思路:
利用快慢指针实现
prev node next 中间节点就是慢指针, 快指针走两步就能得到node
快慢指针的方式 S 一次走一步,F 一次走两步,当F为NULL时此时S指针的位置就是中间的节点
也可以将链表遍历一遍,等于中间节点/2+1就可以了. (不管奇偶都是除以2+1)
4.输入一个链表,输出该链表中倒数第k个节点.
基本思想:
让快的指针先走完k步之后,然后slow和fast同时进行走;当fast等于NULL时,此时输出slow,就为恰好倒数k的节点
方法1:快慢指针
让快指针先走k步;先后快慢指针同时走
class Solution {
public:
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
ListNode* slow, *fast;
slow = fast = pListHead;
while (k--) {
if (fast) { //越界问题
fast = fast->next;
}
else {
return NULL;
}
}
while (fast) {
slow = slow->next;
fast = fast->next;
}
return slow;
}
};
方法2:先统计链表整体的长度,然后size-k的位置就是刚好倒数的位置
class Solution {
public:
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
ListNode* cur = pListHead;
if(cur == nullptr)
return nullptr;
int size = 0;
while(cur) {
cur = cur->next;
size++;
}
//越界问题
if (k > size)
return nullptr;
int len = size - k;
cur = pListHead;
for(int i= 0; i < len; i++) {
cur = cur->next;
}
return cur;
}
};
5.将两个有序的链表合并为一个新的链表,新链表通过拼接给定的两个链表所有节点组成.
求解步骤:
1.确定新的头结点 newH(头结点) newT(尾结点)
2.合并两个有序链表
3.将没有全部插入的节点全部放入到新的尾结点中
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if (l1 == NULL)
return l2;
if (l2 == NULL)
return l1;
//比较找到新的头部和尾部
ListNode* newHead, *newTail;
if (l1->val <= l2->val) {
newHead = newTail = l1;
l1 = l1->next;
}
else{
newHead = newTail = l2;
l2 = l2->next;
}
//进行合并链表
while (l1 && l2) {
if (l1->val <= l2->val) {
newTail->next = l1;
l1 = l1->next;
}
else{
newTail->next = l2;
l2 = l2->next;
}
newTail = newTail->next;
}
//将没有合并完的链表进行重新连接
if (l1 == NULL)
newTail->next = l2;
if (l2 == NULL)
newTail->next = l1;
//返回头部
return newHead;
}
};
6.以给定值x 为基准,将链表分割成两部分,所有小于x的节点排在大于等于x的节点之前.(分割链表)
基本思路:
- 创建大于x的头部和尾部; 创建小于x的头部和尾部
- 然后比较大小重新进行链接即可
- 对节点更新处理
class Partition {
public:
ListNode* partition(ListNode* pHead, int x) {
//创建了大于x的头部和尾部
ListNode* GH, *GT, *LH, *LT, *cur;
//初始化操作
GH = GT = new ListNode(0);
LH = LT = new ListNode(0);
while (pHead == nullptr) {
return nullptr;
}
//将大于小于的节点重新连接起来
cur = pHead;
while (cur) {
if (cur->val < x) {
LT->next = cur;
LT = LT->next;
}
else {
GT->next = cur;
GT = GT->next;
}
cur = cur->next;
}
//将连接成功的节点进行初始化更新处理
GT->next = nullptr;
LT->next = GH->next;
return LH->next;
}
};
7.链表的回文结构
解题思路
- 先找到中间节点
- 给中间节点之后的聊表进行逆置操作
- 然后将逆置之后的链表与逆置之前的链表进行比较
class PalindromeList {
public:
bool chkPalindrome(ListNode* A) {
// write code here
ListNode* slow, *fast;
slow = fast = A;
//1. 寻找链表的中间节点
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
}
ListNode* prev, *cur;
prev = nullptr;
cur = A;
//2.链表的逆置操作
while (cur) {
ListNode*next = cur->next;
cur->next = prev;
prev = cur;
cur = next;
}
cur = prev;
//3.进行对应元素比较操作
while (A->val != cur->val) {
return false;
A = A->next;
cur = cur->next;
}
return true;
}
};
8.输入两个链表找出他们相交的节点,
基本思路:
- 先分别遍历两个单链表, 求出长度
- 求出两个长度的绝对值; 让长的链表先走差值的长度
- 两个链表同时向后走, 看是否相等即可
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode *curA = headA;
ListNode *curB = headB;
int idxA = 0;
int idxB = 0;
while (curA) {
idxA++;
curA = curA->next;
}
while (curB) {
idxB++;
curB = curB->next;
}
//求出两个链表的差值长度
int idx = abs(idxA - idxB);
//
//依据上面遍历的链表的长度来进行判断哪个链表长
if (idxA > idxB) {
curA = headA;
curB = headB;
}
else{
curA = headB;
curB = headA;
}
//长的走idx步
while (idx--) {
curA = curA->next;
}
//两个链表同时向后走, 看是否相等即可
while (curA && curB) {
if (curA == curB)
return curA;
else
curA = curA->next;
curB = curB->next;
}
return nullptr;
}
};
9.环形链表(快慢指针)
基本思路:快慢指针
一个走一步, 一个走两步; 判断是否相等即可
class Solution {
public:
bool hasCycle(ListNode *head) {
if (head == nullptr)
return false;
ListNode *slow, *fast;
slow = fast = head;
while (fast && fast->next && slow) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast)
return true;
}
return false;
}
};
为什么要满足快的走两步?慢的走一步?
假如不是, 有可能错过相遇点, 那么快的有可能超前;导致他们不会再相遇
10.环形链表2
基本思路:
- 找到相遇的点
- 看相遇的点是不是头结点
class Solution {
public:
//寻找相遇点的函数
ListNode *Node(struct ListNode *head) {
ListNode *fast, *slow;
if (head == NULL) {
return NULL;
}
fast = slow = head;
while (fast && fast->next) {
fast = fast->next->next;
slow = slow->next;
if (fast == slow)
return fast;
}
return NULL;
}
ListNode *detectCycle(ListNode *head) {
ListNode * cur = Node(head); //寻找相遇的点
//寻找入环的节点
while (cur) {
// while (cur != head) { //假如相遇的点不等于头结点时,那么cur和head继续往后走
// cur = cur->next;
// head = head->next;
// }
// return cur;
if (cur == head) //看相遇的点是不是头结点; 进行比较
return cur;
else
cur = cur->next;
head = head->next;
}
return NULL;
}
};
11.对链表进行插入排序 (对已经有序的数据中,去找一个合适的位置插入一个数据 ,并且插入之后这个数据仍然是有序的)
基本思路: 先定义节点
始终认为第一个节点是有序的
然后从start的位置往后面进行查找, 找到第一个大于cur, 然后重新进行数据的连接
class Solution {
public:
ListNode* insertionSortList(ListNode* head) {
struct ListNode* cur, *prev, *start, *newH;//当前位置 , 前驱 ,开始位置 ,新的头 ,
if (head == NULL || head->next == NULL) {
return head;
}
//对定义的三个元素进行初始化
prev = head; //前驱
cur = head->next;//待排序的链表
//创建的新的头部
newH = new ListNode(0);//新的头
newH->next = head;
while (cur) {
//相邻节点符合要求此时不需要排序
if (cur->val >= prev->val) { //说明不需要排序
prev = cur;
cur = cur->next;
}
else {
//从表头开始找第一个大于cur的节点
start = newH; //新链表头的变化
///
//该循环是找到合适的位置;
while (start->next && start->next->val <= cur->val) {
start = start->next; //进行下一个链表的
}
//找到合适的位置之后进行数据的连接
//先连接未排序之前, 去掉cur之后的prev的指向链接
//
//start cur start->next 进行这三个节点的重新连接
prev->next = cur->next; //将节点进行换序插入排序
cur->next = start->next;
start->next = cur;
//
//指向下一个需要排序的节点的位置
cur = prev->next;
}
}
cur = newH->next;
delete newH;
return cur;
}
};
12.在一个排序的链表中,存在重复的节点,删除链表中重复的节点,重复的节点不保留,返回链表的头指针.
class Solution {
public:
ListNode* deleteDuplication(ListNode* pHead) {
if (pHead == NULL) {
return NULL;
}
ListNode* cur = pHead;
ListNode* prev = NULL;
ListNode* next = cur->next;
ListNode* tmp;
///
while (next) {
if (cur->val != next->val) {
prev = cur;
cur = next;
next = next->next;
}
else{
/****************************************
//找第一个不重复的节点
while (next && next->val == cur->val)
next = next->next;
//释放重复的区间
//依次循环释放cur节点; 执行完之后肯定跳到了next的位置
//&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
//依次循环删除cur节点
while (cur != next) {
//先把当前节点的下一个位置进行保存下来
tmp = cur->next; //循环释放节点
delete cur;
cur = tmp;
}//上述删完之后cur肯定走到next的位置
//&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
//重新链接
if (prev == NULL) {
pHead = cur;
}
else {
prev->next = cur;
}
if (next)
next = next->next;
/***************************************
}
/
}
return pHead;
}
};
双向带头循环链表
List.c文件
#include "List.h"
#include <stdio.h>
#include <stdlib.h>
void listInit(List* lst) {
//构建一个头结点,构成循环结构
lst->_header = creatNode(0);
lst->_header->_next = lst->_header;
lst->_header->_prev = lst->_header;
}
Node* creatNode(Type data) {
Node* node = (Node*)malloc(sizeof(Node));
node->_data = data;
node->_next = NULL;
node->_prev = NULL;
return node;
}
void ListPushBack(List* lst, Type data) {
Node* node = creatNode(data);
Node* last = lst->_header->_prev;
//last node header
last->_next = node;
node->_prev = last; //先连接内部的
node->_next = lst->_header;
lst->_header->_prev = node; //连接外部的线
//ListInsert(lst->_header, data);
}
void ListPopBack(List* lst) {
Node* last, *prev;
//注意: 不能删除header
if (lst->_header->_next == lst->_header) { //表示一个空链表 ,没有数据可以删除
return;
}
last = lst->_header->_prev;
prev = last->_prev;
prev->_next = lst->_header;
lst->_header->_prev = prev;
free(last);
ListErase(lst->_header->_prev);
}
void ListPushFront(List* lst, Type data) {
// header node front
Node* node = creatNode(data);
Node* front = lst->_header->_next;
node->_next = front;
front->_prev = node;
node->_prev = lst->_header;
lst->_header->_next = node;
//ListInsert(lst->_header->_next, data);
}
void ListPopFront(List* lst) {
/*Node* front, *next;
if (lst->_header == lst->_header->_next) {
return;
}
front = lst->_header->_next;
next = front->_next;
free(front);
lst->_header->_next = next;
next->_prev = lst->_header;*/
ListErase(lst->_header->_next);
}
//给当前位置的前面插入一个data
void ListInsert(Node* pos, Type data) {
Node* prev = pos->_prev;;
Node* node = creatNode(data);
//prev pos node
prev->_next = node;
node->_prev = prev;
node->_next = pos;
pos->_prev = node;
}
//当前位置的前面进行删除
void ListErase(Node* pos) {
Node* prev, *next;
//删除当前位置的节点
//注意hearder 不能进行删除
if (pos->_next == pos) { //表示空
return;
}
prev = pos->_prev;
next = pos->_next;
free(pos);
prev->_next = next;
next->_prev = prev;
}
void ListPrint(List* lst) {
Node* cur = lst->_header->_next;
while (cur != lst->_header) {
printf("%d ", cur->_data);
cur = cur->_next;
}
printf("\n");
}
list.h文件
typedef int Type;
typedef struct Node{
Type _data;
struct Node* _next;
struct Node* _prev;
}Node;
typedef struct List{
Node* _header;
}List;
void listInit(List* lst);
Node* creatNode(Type data);
void ListPushBack(List* lst, Type data);
void ListPopBack(List* lst);
void ListPushFront(List* lst, Type data);
void ListPopFront(List* lst);
//给当前节点的下一个位置插入一个data
void ListInsert(Node* pos, Type data);
//当前节点的下一个位置删除
void ListErase(Node* pos);
void ListDestory(List* lst);
void ListPrint(List* lst);
test.c文件
#include "List.h"
#include <stdio.h>
#include <stdlib.h>
void test1() {
List lst;
listInit(&lst);
ListPushBack(&lst, 1);
ListPushBack(&lst, 2);
ListPushBack(&lst, 3);
ListPushBack(&lst, 4);
ListPushBack(&lst, 5);
ListPushBack(&lst, 6);
ListPrint(&lst);
//
//ListPopBack(&lst);
//ListPrint(&lst);
//ListPopBack(&lst);
//ListPrint(&lst);
//ListPopBack(&lst);
//ListPrint(&lst);
//ListPopBack(&lst);
//ListPrint(&lst);
//ListPopBack(&lst);
//ListPrint(&lst);
//ListPopBack(&lst);
}
void test2() {
List lst;
listInit(&lst);
ListPushFront(&lst, 1);
ListPrint(&lst);
ListPushFront(&lst, 2);
ListPrint(&lst);
ListPushFront(&lst, 3);
ListPrint(&lst);
ListPushFront(&lst, 4);
ListPrint(&lst);
ListPopFront(&lst);
ListPrint(&lst);
ListPopFront(&lst);
ListPrint(&lst);
ListPopFront(&lst);
ListPrint(&lst);
ListPopFront(&lst);
ListPrint(&lst);
}
int main () {
//test1(); //执行尾插尾删的操作
test2();
//test3();
system("color A");
system ("pause");
return 0;
}
链表和顺序表特点:(面试常考)
1.顺序表
优点: 1>支持随机访问 2>结构实现简单 3>连续结构 4> 尾插 尾删均是O(1)
缺点: 任意位置的插入删除为O(n),有增容代价.
使用场景: 频繁访问场景
2.链表 (双向)
优点: 1>任意位置的插入删除为O(1) 2>没有有增容代价.
缺点: 1>结构实现复杂 2>非连续结构 3>不支持随机访问
使用场景: 频繁插入场景场景