链表
顺序表缺点
插入删除时需要移动大量元素,为此设计链表
链表定义 :
链表由结点组成,每个节点链接串联,分为单向链表,双向链表,循环链表等。
A→B→C→D→NULL
B是C的前驱节点,是A的后继节点
链表优缺点
优点:
1.链式存储,随时增加元素随时分配
2.插入删除时间复杂度O(1)
缺点:
索引查找时间复杂度O(n)
指针概念:
int n = 6;
int *p = #
p = 0x123456
*p = 6
单向链表增删改查:
1.结点创建
/*创建结点结构体*/
struct ListNode{
int val; //数据域,这里定义类型为int
struct ListNode *next; //指针域,指向后继结点地址
//两种构造方式
ListNode(){} //空
ListNode(int x):
val(x),next(NULL){}
};
2.链表创建
头文件:
#include<iostream>
#include<cstdlib>
using namespace std;
//随机链表还有ctime库
尾插法:从链表尾部插入,创建后继节点,后继节点地址赋给前继节点next,指针移到后继节点,注意链表尾节点应为null。
> p:移动指针
> curr:新创结点
> head:头结点
#include<iostream>
using namespace std;
/*创建结点结构体*/
struct ListNode{
int val;
struct ListNode *next;
//两种构造方式
ListNode(){} //空
ListNode(int x):
val(x),next(NULL){}
};
ListNode* CreateListByTail(int a,int b){
ListNode *head = new ListNode(a),*curr,*p; //new+数据类型返回数据地址
p = head;
curr = new ListNode(b);
p->next = curr;
p = curr;
//p->next = Null;
return head;
}
头插法:每次从头结点前进行插入,注意数组插入应为逆序
> p:移动指针
> head:头结点
#include<iostream>
using namespace std;
/*创建结点结构体*/
struct ListNode{
int val;
struct ListNode *next;
//两种构造方式
ListNode(){} //空
ListNode(int x):
val(x),next(NULL){}
};
ListNode* CreateListByHead(int a,int b){
ListNode *head = new ListNode(b),*p;
p = new ListNode(a);
p->next = head;
head = p;
return head;
}
3.链表打印
循环输出所有节点至NULL。
void ListDisplay(ListNode *Head){
ListNode *p = Head
while(p!=NULL){
cout<<p->val<<"->";
p = p->next;
}
cout<<"NULL"<<endl;
}
4.链表索引
对于一个头结点List1,求索引i结点。
时间复杂度O(n),空间复杂度O(1)。
ListNode *GetListNode(int i,ListNode *List1){
ListNode *p = List1;
int j = 0;
while(p&&j<i){
p = p->next;
++j;
}
if(!temp||j>i) //j>i防止i<0
{return NULL;}
return p;
}
4.链表查找
对于一个头结点Head,给定一个值v,找链表数据域上第一个等于v的结点。
时间复杂度O(n),空间复杂度O(1)。
ListNode *FindNodeByValue(int v,ListNode *Head){
ListNode *p = Head;
while(p){
if(p->val == v) return p;
p = p->next;
}
return NULL;
}
4.链表结点插入
对于一个头结点Head,在索引i后插入值为v的结点
时间复杂度O(n),空间复杂度O(1)。
void ListInsertNode(int i, int v, ListNode *Head) {
ListNode *p = Head, *vtx = new ListNode(v);
int j = 0;
while (p && j < i) {
p = p->next;
++j;
}
if (p || j <= i) {
vtx->next = p->next;
p->next = vtx;
}
}
4.链表结点删除
对于一个头结点Head,删除索引i处的结点
注意使用函数释放内存
时间复杂度O(n),空间复杂度O(1)。
void ListDeleteNode(int i, ListNode *Head) {
ListNode *p1 = Head, *p2;
int j = 0;
while (p1 && j < i) {
p2 = p1;
p1 = p1->next;
++j;
}
if (p1 || j <= i) {
if (p1 == Head) {
Head = p1->next;
delete p1;
} else {
p2->next = p1->next;
delete p1;
}
}
}
5.链表销毁
链表所有结点内存释放,头结点置空。
注:头结点需要使用二级指针 ,因为链表头要置空,普通指针传参无法使外部指针变量变化,外部会得到一个内存已经释放的野指针。
时间复杂度O(n),空间复杂度O(1)。
void ListClear(ListNode **Head){
ListNode *pHead = *Head;
while(pHead){
ListNode *p = pHead->next;
delete pHead;
*pHead = p;
}
*Head = NULL;
}
5.链表翻转
ListNode* reverse(ListNode *head){
ListNode *p1 = NULL; //一个前值
ListNode *p2 = head;
ListNode *p3 = p2;//一个后值
while(p2){
p3 = p2->next;
p2->next = p1;
p1 = p2;
p2 = p3;
}
return p1;
}
算法:
1.双指针
1.链表合并——双指针各指一链表再合并
2.获得倒数第k个结点/中点——快慢指针
4.环/相交——快慢指针,找关系确定快慢指针步数
2.哈希表
需要记录链表中val值时
3.虚拟头结点
头结点有可能发生改变时,可以创建虚头结点(hair)
ListNode *hair = new ListNode(0,head);
head = hair->next
//虚拟头节点的下一个地址指向头结点。
3.双向链表(待整理)
注意事项:
1.赋值后先移位
2.判断清楚交换顺序(画图)
参考:
1.画解链表
2.C++:数据结构-链表创建
3.C++ 单链表创建、插入和删除