https://www.bilibili.com/video/BV1xK4y1U7Dc 后(复杂度、顺序表、链表)笔记
学数据结构,也就是算法并不单纯是应付考试,应付考试我不必写那么多,主要是做一个深刻的了解,平时多练练力扣的题 做不出来也要找出规律 https://leetcode-cn.com/
(数据结构课本都是使用C语言作为示例)代码重构,提高后台响应速度,内存溢出,面试时我们会想到算法,工作中其实比较少使用,但不代表你不学,它类似木桶效应的短板。“ MySql 为什么索引要用 B+ 树”, 那什么是B+树,红黑树,你说不出一句话来,尽管它就是底层原理
编译工具 : Microsoft Visual Studio
课本的课后习题 也要看
额,做不出来是其次,大部分情况是看不懂题意。。。哪位大佬可以指导一下
总结一: 做题前要先画图 不容易错
算法的设计要求包括,正确性、可读性、健壮性和效率与低储存量需求。
(要改代码,尽量用接口,有问题时,不要用那个类,加个类进去)
数据结构中,从逻辑上可以把数据结构分为 线性结构和非线性结构。
计算机算法指的是解决问题的有限步骤。
复杂度
复杂度是用来分析程序和算法运行的时间和空间占用情况。
时间复杂度
算法中的基本操作的执行次数,为算法的时间复杂度(时间复杂度不是计算程序运行多长时间,因为运行时间和设备上硬件有关。)
计算时间复杂度 你可以运行一下C程序知道它的运算次数
void fun(){
int count=0;
for(int i=0;i<N;i++){
for(int j=0;j<N;j++){
++count;
}
}
for(int k=0;k< 2*N ; ++k){
++count;
}
int M=10;
while(M--){
++count;
}
print("%d\n",count);
}
F(N) = N^2 + 2*N+10
大O的渐进表示法: 只需要大概执行次数
1、用常数1取代运行时间中的所有加法常数
2、在修改后的运行次数函数中,只保留最高阶顶
3、如果最高阶顶存在且不是1,则去除与这个项目相乘的常数,得到的就是大O阶
这里使用大O的渐进表示法后, 时间复杂度为 O(N*N) N平方
O(1)不是只找一次 而是常数个
二分查找时间复杂度O(log2n) ,
最好的情况是O(1) 折半查找元素 一张纸对折 在大于中间值或小于中间值找,缩小一半的查找
计算过程:N/2/2/2../2 =1 N= 2^X 二的X次方 X是一张纸对折次数(查找次数)此时我们要的是X, 所以 x=long long Fib(size_t N){ return N<2 ? N : Fib(N-1)*Fib(N-2); } 递归算法复杂度如何计算: 递归次数*每次递归函数的次数 斐波那契数列,下面画一个二叉树帮助理解 1 fib(n) 2 fib(n-1) fib(n-2) 4 fib(n-2) fib(n-3) fib(n-3) fib(n-4) 8 个fib = 2^3 答案是 2^0 + 2^1 + 2^2+...+2^(n-1) = 2^n - 1 即O(2^N) 测试一下Fib(100)到底要运行多久,会不会卡死,运行2的100次方 有30个0的数,我的计算机CPU目前是2.90GHz 也就是29亿次运行每秒,emem太大了等不了 运行Fib(50)吧 递归计算斐波那契数列有非常多的重复 fib(n-1)返回fib(n-2) fib(n-3)。。,其中fib(n-2)重复计算了 先定义数组,用空间换时间,时间复杂度为O(N) #include<stdio.h> #include<malloc.h> long long* Fib(size_t N){ long long* fibArray = malloc(sizeof(long long)*N); fibArray[0] = 0; //N是0直接返回0 if(N==0) return fibArray; fibArray[1] = 1; for(int i=0; i <= N; ++i){ fibArray[i] = fibArray[i-1] + fibArray[i-2] } return fibArray; } 可以直接计算出 printf("%d\n",Fib(1000));
斐波那契数列时间复杂度为O(2^N),空间复杂度是
描述 复杂度由低到高 说明 举例 对数级别 logN 二分策略 二分查找 线性级别 N 循环 找出最大元素 线性对数级别 NlogN 归并排序 指数级别 2^N 穷举查找 检查所有子集 2^N 和 N^2 大小问题: 2^0=1 0^2=0 2^10=1024 10^2=100
空间复杂度
几乎不关注空间复杂度,摩尔定律:集成电路上可以容纳的晶体管数目在大约每经过18个月便会增加一倍,也就是内存每过1年半翻一倍;设备上的内存一直在变高。只有嵌入式很在意空间,设备大小手限制。
时间复杂度不计算时间,计算大概运算次数。
空间复杂度不计算空间(占用多少字节),计算大概定义的变量个数
上面的Fib(){} ,开辟了N个空间, 空间复杂度是O(N)
算法复杂度 3次循环就是
线性
数组和链表是基础,要掌握 插入删除需要什么操作 比如说数组的移动位置和链表的指针指向
线性表
线性表是N个具有相同特性的数据元素的有限序列。属于线性结构,也就是连续的一条直线,常见的线性表: 数组(顺序表),链表,栈,队列,字符串
线性表是一个有限序列,可以为空 (空表)
线性表第一个元素没有直接前驱,最后一个元素没有直接后继
存储结构有顺序存储结构和链式存储结构两种,前者称为顺序表,后者称为链表。存储结构就是物理结构,在内存中的数据的地址不一定是连续的。线性表只是在逻辑结构上是线性的。物理地址连续的存储单元是顺序表。
假设线性表有n个元素,如果在第i个位置插入一个新元素,需向后移动( ) 个元素
程序中 end=n-1 n-1>=i时 后移 ps->a[end+1] = ps->a[end];
需要后移的数量为 n-i+1
设一个链表最常用的操作是在末尾插入节点和删除尾节点,则选用(D)最省时间。
某线性表中最常用的操作是在最后一个元素之后插入一个元素和删除第一个元素,则采用()存储方式最节省运算时间,选的答案是:带尾指针的单循环链表
A、单链表 B、单循环链表 C、带尾指针的单循环链表 D、带头节点的双循环链表
最省时间说明要避免遍历整个链表,链表末尾操作说明是循环链表,
单链表无法得到前一个节点,删除尾节点和在链表末尾插入节点也要遍历全表
带尾指针的单向链表:插入可以,但是删除无法完成,因为p需要前移,但是单向链表无法得到前一个节点。
带尾指针的双向链表:插入和删除都很简单。
带尾指针的单向循环链表:插入很简单,删除则需要遍历整个链表,比较费时。
带头指针的双向循环链表:插入和删除都很简单。
链表与顺序表区别
- 链表
优点: 插入和删除不需要移动其它元素,空间有效利用(扩容方便)
缺点: 大量访问操作时不如顺序存储结构 不能随机访问
- 顺序
优点: 1、可随机存取,存储密度高; 2、缓存利用率比较高
缺点: 1、插入或删除操作时,需大量移动元素 时间复杂度O(N)
2、 要求连续的内存空间,空间不够时,增容会消耗内存(可能存在空间浪费,增容有一些效率损失)
顺序表的缺点由链表解决了,链表和顺序表是互补的数据结构
理解即可:
什么叫随机访问? 像数组,直接访问第三个位置 第四个位置;而链表不行
CPU计算时先看缓存中有没有,没有才到内存中找:(寄存器空间很小(几k)不可能直接存放一个数组)
系统预加载,访问一个数据时,不会只加载一个数据到缓存,而是这个数据开始一段数据到缓存。
数组的预加载很快,因为物理地址是连续的;
顺序表
物理地址连续的存储空间是顺序表。
中间/头部插入删除,时间复杂度是O(N)
.h文件是头文件,内含函数声明、宏定义、结构体定义等内容 .c文件是程序文件,内含函数实现,变量定义等内容,下面文件是 SeqList.h
#pragma once
//顺序表,有效数组在数组中必须是连续的 #define只能定义常量 这里用typedef定义int类型变量,以后返回类型你想改,这里改全局改
//静态顺序表设计 --- 固定大小
//常用的都是动态的顺序表 capicity空间不够则扩容
//typedef int SLDataType;
//#define N 10
//struct SeqList
//{
// SLDataType* a; //指向动态开辟的数组 静态不传指针,使用这一条SLDataType a[N];
// int size; //有效数据个数
// int capicity; // 动态顺序表增加 容量空间的大小字段
//};
//可以用typedef定义 简写为SL
typedef struct SeqList
{
SLDataType* a; //指向动态开辟的数组
size_t size; //有效数据个数
size_t capicity; //容量空间的大小
}SL, SeqList;
//SeqList.c
#include "SeqList.h"
//初始化
//void slInit(SL* ps);//, size_t capicity
//顺序表打印
//void slPrint(SL* ps);
//尾插尾删 头插头删 中间插入删除
//void slPushBack(SL* ps, SLDataType x); //struct SeqList简写为SL
//void slPopBack(SL* ps);
//void slPushFront(SL* ps, SLDataType x);
//void slPopFront(SL* ps);
//在pos位置插入X ,删除pos位置的值
//void SeqListInsert(SL* ps,int pos,SLDataType x);
//void SeqListErase(SL* ps,int pos);
//顺序表销毁
//void slDestory(SeqList* ps);
//检查空间,如果满了,进行扩容
//void checkCapicity(SeqList* ps);
//查找
//int slPrint(SL* ps, SLDataType x);
//初始化
void slInit(SL* ps){//, size_t capicity
ps-> a= (SLDataType*)malloc(sizeof(SLDataType) *4);
if(ps->a == NULL){
printf("申请内存失败\n");
exit(-1);
}
ps->size = 0;
ps->capacity = 4;
}
//尾插
void slPushBack()(SL* ps,SLDataType x){
assert(ps); // 断言 ps指针不能为空
checkCapicity(ps);
ps->a[ps->size] = x; //放到数组的 a[size]位置 size=3 a[3]越限
ps->size++;
}
//打印
void slPrint(SL* ps){
assert(ps);
for(int i=0;i< ps->size;++i){
printf("%d", ps->a[i]);
}
}
//尾删
void slPopBack()(SL* ps, SLDataType x){
assert(ps);
//如果这里的a[ps->size-1]值是0 没报错??? 好吧 不删内存值,直接减数组长度,找回的时候,可以找回
//ps->a[ps->size-1] = 0;
ps->size--;
}
//头加
void slPushFront()(SL* ps){
assert(ps);
checkCapicity(ps);
int end = ps->size-1;
while(end >= 0){//数组第0位也要移动
ps->a[end-1] = ps->a[end]; //end的值挪到end+1的位置
--end;
}
ps->a[0] = x;
ps->size++;
}
//头删
void slPopFront(SL* ps){
assert(ps);
int start =0;
while(start< ps->size-1){
ps->a[start] = ps ->a[start+1];
++start;
}
ps->size--;
}
检查空间,如果满了,进行扩容
void checkCapicity(SeqList* ps){
//如果满了需要扩容(如果原地有足够空间直接原地扩容,没有则另找一个新空间,把数组拷过去再释放掉旧的空间)
if(ps->size >= ps->capacity){
//通常增2倍
ps->capacity *=2;
ps->a = (SLDataType*)realloc(ps->a, sizeof(SLDataType)*ps->capacity);
if(pa->a == NULL){
printf("扩容失败\n");
exit(-1);
}
}
}
void slDestory(SL* ps){
free(ps->a); //空间释放
ps->a = NULL; //指针设置为空 防止野指针
ps->size = ps->capacity =0;
}
//在pos位置插入X (插入位置往后全部挪一位) ,删除pos位置的值(往前挪一位)
void SLInsert(SL* ps,int pos,SLDataType x){
assert(ps);
assert(pos < ps->size && pos >=0); //断言为真就没事
checkCapicity(ps);
int end = ps->size-1;
while(end >= pos){
ps->a[end+1] = ps->a[end];
--end;
}
ps->a[pos] = x;
ps->size++;
}
void SLErase(SL* ps,int pos){
assert(ps);
assert(pos <= ps->size && pos >=0);
int start= pos;
while(start< ps->size-1){
ps->a[start] = ps ->a[start+1];
++start;
}
ps->size--;
}
//查找下标
int slPrint(SL* ps, SLDataType x){
assert(ps);
int i=0;
while(i< ps->size){
if(ps->a[i] == x){
return i;
}
}
return -1;
}
free(ps) 释放的是指针还是内存?释放的是指针指向的内存
内存泄露 是指针丢了还是内存丢了? 指针丢了
//test.c
#include "SeqList.h"
//测试头尾插入删除
void TestSeqList1(){
SeqList s;
slPushBack(&s,1);
slPushBack(&s,2);
slPushBack(&s,3);
slPushBack(&s,4);
slPrint(&s);
slPopBack(&s);
slPopBack(&s);
slPrint(&s);
slPushFront(&s,-1);
slPushFront(&s,-2);
slPrint(&s);
}
算法题 移除元素 数组删除指定元素
如果按照正常的解法,那么数组 3 1 6 3 5 3 7循环
one: 正常的解法 时间复杂度O(N^2) 空间复杂度O(1)
for(int i=0;i<a->size;i++){
if(a[i]==3){
//后面的数字往前挪
for(int start=i; start<a->size-1; ++start){
ps->a[start] = ps ->a[start+1];
}
a->size--;
}
}
two: 不考虑空间复杂度,直接复制一个数组
做法: 定义一个新数组,遍历原数组,发现不是3就给添加到新数组里面;遍历完成后吧新数组复制到原数组,新数组删掉,返回数组长度
突然发现题干要求的是返回数组长度不是输出删除元素后的数组,,,所以我们现在可以不定义新数组,直接遍历原数组,发现不是3就计数+1
for(int i=0;i<a->size;i++){
if(a[i]==3){
++count;
}
}
three : 前面的做法依然不符合时间复杂度O(N) 使用双指针
public int removeElement(int[] nums, int val) {
if (nums == null || nums.length == 0)
return 0;
int i = 0,j =0;
int length= nums.length;
while(i<length){
if(nums[i] != val){
nums[j] = nums[i];
j++;
}
i++;
}
return j;
}
双指针方法解可以解决时间复杂度问题
链表
非连续的物理存储结构; 链表每个元素用指针连接; 现实中最常用,针对顺序表缺点设计的
一些题只要你会就掌握了
1、反向输出带头节点的单链表全部节点的值。
2、找出带头节点的单链表倒数第K个节点。
3、判断单链表是否有环
4、找出两个汇聚单链表的公共节点https://blog.csdn.net/qq_41740162/article/details/105349989
1、时间复杂度O(N) 递归 void printList(ListNode *pp){ if(pp == NULL) return; printList(pp->next); printf("%d",pp->data); return; } 2、找出倒数节点 先将p1向右移动k次,只需要继续保持p1和p2等间距的右移,当p1的next为null,则证明p2所指的结点的值为倒数第k个节点的值 ListNode *FindKNode(LinkList ll,unsigned int k){ ListNode *pp1 = LL->next; ListNode *pp2 = LL->next; int i=0; //先定位到K个节点 while((pp1!=NULL) && (i<k)){ pp1 = pp1->next; i++; } if(ppl==Null) return NULL; while(1){ //pp1继续直到null 此时的pp2就是倒数几个 ppl = ppl->next; if(ppl==Null) break; pp2 = pp2->next; } return pp2; } 3、“快慢指针”的方法。就是有两个指针fast和slow,开始的时候两个指针都指向链表头head,然后在每一步 操作中slow向前走一步即:slow = slow->next,而fast每一步向前两步即:fast = fast->next->next。 两者之间的距离逐渐缩小:...、5、4、3、2、1、0 -> 相遇。又因为在同一个环中fast和slow之间的距离不会大于换的长度,因此到二者相遇的时候slow一定还没有走完一周(或者正好走完以后,这种情况出现在开始的时候fast和slow都在环的入口处) typedef struct node{ char data ; node * next ; }Node; bool exitLoop(Node *head) { Node *fast, *slow ; slow = fast = head ; while (slow != NULL && fast -> next != NULL) { slow = slow -> next ; fast = fast -> next -> next ; if (slow == fast) return true ; } return false ; }
单链表
链表在物理上是上一个节点存下一个节点的地址
节点next 用于指向下一个节点的指针 链表的最后存的是0X0000 0000
大部分的题都是带头单链表为主(双向链表不好出题 -_-!),但在实际开发中是双向链表为主
SList.h
#pragma once
#include<stdio.h>
//节点
typedef struct SListNode
{
SListDataType* data; //
struct SListNode* next;
}SLTNode;
void SListPopBack(SListNode** phead);
void SListPushBack(SListNode** phead,SListDataType x);
void SListPushFront(SListNode** phead,SListDataType x);
void SListPopFront(SListNode** phead);
void SListSize(SListNode* phead);
void SListPrint(SListNode* phead)
SList.c
#pragma once
#include "SList.h"
#include<stdio.h>
#include<stdlib.h>
void SListPrint(SListNode* phead){
SListNode* cur = phead;
while(cur!=NULL){
printf("%d",cur->data);
cur = cur->next; //重点: 下一个节点地址付给当前节点
}
}
void SListSize(SListNode* phead){
}
//链表尾部插入
//SListNode* phead是指针,我传指针给你,你对指针进行赋值,返回该指针,会不会影响外边的指针? 不会,应该传地址 也就是二级指针 而只读的传指针*就够了
void SListPushBack(SListNode** pphead,SListDataType x){
SListNode* newNode = BuySListNode(x);
if(*pphead == NULL){
*pphead = newNode;
}else{
//找到尾部的地址 尾部节点地址->next为NULL
SListNode* tail = *pphead;
while(tail->next!=NULL){
tail = tail->next; //重点: 下一个节点地址付给当前节点
}
tail->next = newNode;
}
}
SListNode* BuySListNode(SListDataType x){
SListNode* newNode = (SListNode*)malloc(sizeof(SListNode));
if(newNode == NULL){
printf("申请节点失败\n");
exit(-1);
}
newNode->data = x;
newNode->next = NULL;
return newNode;
}
//链表尾部删除
void SListPopBack(SListNode** pphead){
//空
//一个节点
//一个节点以上
if(*pphead == NULL){
return;
}
else if((*pphead)->next == NULL){
free(*pphead); //就一个节点 删了就没了
*pphead = NULL;
}else{
SListNode* prev = NULL; //倒数第二个链表的地址
SListNode* tail = *pphead;
while(tail->next!=NULL){
prev = tail;
tail = tail->next;
}
free(tail);
prev->next = NULL;
}
}
//头插
void SListPushFront(SListNode** phead,SListDataType x){
SListNode* newNode = BuySListNode(x);
newNode->next = *phead;
*pphead = newNode;
}
//头删
void SListPopFront(SListNode** phead){
//空
//一个节点和一个节点以上
if(*pphead == NULL){
return;
}
else{
SListNode* next = (*pphead)->next;
free(*pphead);
*pphead= next;
}
}
//单链表查找
SListNode* SListFind(SListNode* phead,SListDataType x){
SListNode* cur = phead;
while(cur){
if(cur->data == x){
return cur;
}
cur = cur->next;
}
return NULL;
}
//给出insert值查找
SListNode* SListFind(SListNode* phead,unsigned int index){
if(phead == NULL){
return;
}
int i=0; //指向第几个节点
SListNode* cur = phead;
while(cur!=NULL && (i < index)){
cur = cur->next; i++;
}
if(!cur){ return NULL; }//超出了表长
return cur;
}
//中间插探讨 为什么是后面插,因为单链表你要前面插需要找到它前面是谁,我们单链表只记录后面一个节点的指针
// newnode新生成的要插入的节点 pos 新生成的的节点之前的节点
pos->next = newnode;
newnode->next = pos->next;
//因为 pos->next = newnode; 导致下一行的原本pos->next的值是空 必须先保存原本pos->next的值 s所以我们把两个式子颠倒即可
newnode->next = pos->next;
pos->next = newnode;
void SListInsetAfter(SListNode* pos,SListDataType x){
assert(pos);
SListNode* newNode = BuySListNode(x);
newNode->next = pos->next;;
pos->next = newNode;
}
//删除
void SListInsetAfter(SListNode* pos,SListDataType x){
assert(pos);
if(pos->next){
SListNode* next = pos->next;
SListNode* nextnext = next->next;
pos->next = nextnext;
free(next);
}
}
//test.c
#include "SList.h"
//测试头尾插入删除
int main(){
SListNode* pList = NULL;
SListPushBack(&pList,1);//SListPushBack(pList,1); 形参改成SListNode**了,应该传指针的地址
SListPushBack(&pList,2);
SListPushBack(&pList,3);
SListPushBack(&pList,4);
SListPrint(pList);
SListPopBack(&pList);
SListPopBack(&pList);
SListPrint(pList);
return 0;
}
反转链表(力扣) 经常遇到
解题思路:
假设n2 当前指针指向1,然后n1就是n2的前一个节点,指针为NULL,n3为当前指针的下一个指针
当n2不为NULL时,进行循环。
n1 n2 n3
null 1 ->2 -> 3 -> 4 -> 5 -> null
n1 n2 n3
null 1 ->2 -> 3 -> 4 -> 5 -> null
n1 n2 n3
null 1 ->2 -> 3 -> 4 -> 5 -> null
n1 n2 n3
null 1 ->2 -> 3 -> 4 -> 5 -> null
n1 n2 n3
null 1 ->2 -> 3 -> 4 -> 5 -> null
n1 n2 n3
null 1 ->2 -> 3 -> 4 -> 5 -> null 结束
第二种思路: 原地反转链表
原数组头节点直接指向空 5先插进去 然后插4 按要求插的个数结束后,其它为插入的数据直接全部头插
翻转前 头节点head->1 ->2 -> 3 -> 4 -> 5 -> null
翻转 头节点head-> null
翻转 头节点head->5 -> null
翻转 头节点head->5 -> 4 -> null
翻转后 头节点head->5 -> 4 -> 3 -> 2 ->1 -> null
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* reverseList(struct ListNode* head){
if(head==NULL || head->next == NULL){
return head;
}
struct ListNode* n1 = NULL, *n2=head, *n3= head->next;
while(n2){
//反转
n2->next = n1;
//迭代
n1 = n2;
n2 = n3;
if(n3)
n3 = n3->next;
}
return n1;
}
//上面是视频的老师的写法,本人想不到用struct ListNode* n1 = NULL, *n2=head, *n3= head->next;定义。。。写成这种
ListNode* n1 = NULL;
ListNode* n2=head;
while(n2){
ListNode* n3= head->next;
//反转
n2->next = n1;
//迭代
n1 = n2;
n2 = n3;
if(n3)
n3 = n3->next;
}
第二种思路: 原地反转链表
struct ListNode* reverseList(struct ListNode* head){
if(head==NULL || head->next == NULL){
return head;
}
ListNode* ss = head->next,
ListNode* ssnext;
while(ss){
//保留ss下一个节点的地址
ssnext = ss->next;
//迭代 在head后插入ss节点
ss->next = head->next;
head->next = ss;
ss= ssnext;
}
return ss;
}
双向链表
每一个节点包含两个指针,一个指向前节点prev, 一个指向next节点
链表一个节点都没有(链表为空)时, head next prev 都指向自己。
下图是带头双向循环链表 特点:结构复杂,操作简单
有空再画图吧
尾插 假设有phead的指针 它有一个指向尾部的指针,不用找尾
( 我为什么把链表的名字定义成phead ; 链表首地址就是head ; path=head head头)
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data; //
struct ListNode* next;
struct ListNode* prev;
};
//尾插
void ListPushBack(ListNode* phead, LTDataType x){
assert(phead);
//双向链表最容易找到尾
ListNode* tail = phead->prev;
ListNode* newnode = BuyListNode(x);
// phead ... tail newnode
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
}
// 头插与尾插类似 简单 phead newnode first
ListNode* first = phead->next; ListNode* newnode = BuyListNode(x);
newnode->next = first;
newnode->prev = phead; //这一句我差点写成 phead->prev
phead->next = newnode; // 不是插到头节点左边,而是头节点右边
first->prev = newnode;
//头删 就是删掉first phead first second
ListNode* first = phead->next; ListNode* second = first->next;
phead->next = second;
second->prev = phead;
free(first)
void ListPopBack(ListNode* phead){
assert(phead);
ListNode* tail = phead->prev;
//phead .. tail->prev tail
ListNode* prev = tail ->prev;
prev->next = phead;
phead->prev = prev;
free(tail);
tail = NULL;
}
void ListInit(){ //ListNode** pphead
/*(*pphead) = BuyListNode(0);
(*pphead)->next = *pphead;
(*pphead)->prev = *pphead;*/
ListNode* phead = BuyListNode(0);
phead->next = phead;
phead->prev = phead;
}
ListNode* BuyListNode(SListDataType x){
ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
newNode->data = x;
newNode->next = NULL;
newNode->prev = NULL;
return newNode;
}
void ListPrint(ListNode* phead){
assert(phead);
//循环链表永远不会空,看头节点
ListNode* cur = phead->next;
while(cur != phead){
printf("%d\t", cur->data);
cur = cur->next;
}
}
ListNode* ListFind(ListNode* phead,LTDataType x){
assert(phead);
ListNode* cur = phead->next;
while(cur!=phead){
if(cur_data == x){
return cur;
}
cur = cur->next;
}
return NULL;
}
//在pos前面插x
// posPrev newnode pos 注意插入位置 pos前面
void ListInsert(ListNode* pos,LTDataType x){
assert(pos);
ListNode* posPrev = pos->prev;
ListNode* newnode = BuyListNode(x);
pos->prev = newnode;
newcode->next = pos;
newcode->prev = proPrev;
posPrev->next = newnode;
}
void ListErase(ListNode* pos){
assert(pos);
assert(pos!=phead);
ListNode* posPrev = pos->prev;
ListNode* posNext = pos->next;
posPrev->next = posNext;
posNext->prev = posPrev;
free(pos);
}
//清空数据
void ListClear(ListNode* pphead){
assert(pos);
//清理所有数据节点,保留头节点,可继续使用
ListNode* cur = phead;
while(cur!= phead){
ListNode* next = cur->next;
free(cur);
cur = next;
}
phead->next = phead;
phead->prev = phead;
}
void ListDestory(ListNode** phead){ //因为要修改原值,必须传指针的地址
assert(*pphead);
ListClear(*pphead);
free(*pphead);
//经常忘记把指针设置为空 差点变成野指针了
*pphead = NULL;//phead = NULL;的话这里指针销毁了,但外面实参的指针还在
}
//test.c
#include "SList.h"
//测试头尾插入删除
int main(){
ListNode* phead = ListInit();//&phead
ListPushBack(phead ,1);//SListPushBack(pList,1); 形参改成SListNode**了,应该传指针的地址
ListPushBack(phead ,2);
ListPushBack(phead ,3);
ListPushBack(phead ,4);
ListPrint(phead );
ListPopBack(phead);
ListPopBack(phead);
ListPrint(phead);
return 0;
}
循环链表
循环链表,特点是表中最后一个节点不再是空,而是指向头节点。
可以是单链表也可以是双向链表
循环单链表基本操作类似于单链表,单链表中最后一个节点的条件是
if(p!=NULL) 或 if(p->next!=NULL)
循环链表的是 下一个节点是否是头节点
if(p!=head) 或 if(p->next!=head)
考点:
顺序表合并
(两个升序顺序表合并成一个升序顺序表)归并排序
LA 1 7 8
LB 3 4 9 10
LC
1 -- 3 1比较小 LC 1
7 -- 3 3小 LC 1 3
7 -- 4 4小 LC 1 3 4
7 -- 9 7小 LC 1 3 4 7
8 -- 9 8小 LC 1 3 4 7 8
只剩下LB 合并到LC 具体实现类似力扣 88. 合并两个有序数组 区别是
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n){
//上次: 先拷贝M数组二数据到数组一N,在对数组一进行排序 MN为数组的长度
// 此时时间复杂度为 O( (M+N)* log(M+N)) log以2为底简写成log
// 怎么优化到 O(M+N)
// nums1 = [1,2,3,0,0,0]
// nums2 = [2,5,6]
// num1[0] 比 num2[0] num1[0] 比较小,把num1值加到新数组
// num1[1] 比 num2[0] 相等,把num1都加到新数组
// num1[2] 比 num2[0] num2[0] 比较小,把num2值加到新数组 此时M也就是数组一种存在的数已经和数组二的第一个元素对比完了,新数组为[1,2,2]
// num1[2] 比 num2[1] num1[2] 比较小,把num1值加到新数组
// num1[3]超出范围不比较 剩下num2[1] num2[2]没比较的比较一下复制到新数组
// 申请空间
int* tmp = (int*)malloc(sizeof(int)*(m+n));
int i1 =0,i2=0;
int index =0;
while( i1<m && i2<n ){
if(nums1[i1] < nums2[i2]){
tmp[index] = nums1[i1];
++index;
++i1;
}else{
tmp[index] = nums2[i2];
++index;
++i2;
}
}
while(i1 < m){
tmp[index] = nums1[i1];
++index;
++i1;
}
while(i2 < n){
tmp[index] = nums2[i2];
++index;
++i2;
}
memcpy(nums1,tmp,sizeof(int)*(m+n));
free(tmp);
}
第十五题 合并两个升序单链表:
public ListNode Merge(ListNode list1,ListNode list2) {
if(list1 == null){
return list2;
}
if(list2 == null){
return list1;
}
ListNode head = null; //头结点
ListNode cur = null; //当前连接节点
//遍历两个链表
while(list1 != null && list2 != null){
if(list1.val <= list2.val){//取LA LB中较小者
if(head == null){
//第一次连接新链表
head = cur = list1;
}else{
//连接新链表
cur.next = list1;
//移动连接指针
cur = cur.next;
}
list1 = list1.next;
}else{
if(head == null){
//第一次连接新链表
head = cur = list2;
}else{
//连接新链表
cur.next = list2;
//移动连接指针
cur = cur.next;
}
list2 = list2.next;
}
}
//两个链表其中有一个遍历结束
if(list1 == null){
cur.next = list2;
}
if(list2 == null){
cur.next = list1;
}
//返回新链表头
return head;
}
合并两个循环链表, 一个在前一个在后
已知a[0][0] 为123; a[2][2]则为?
顺序表是连续的, 第三行第三列可以直接推出来哦 2m+2=10 答案是133
123-》
00 01 02 03
10 11 12 13
20 21 22 23
30 31 32 33
a[3][3] == 123+ 3m+3 = 138
顺序结构(数组)可以存储非线性的,但浪费空间。
已知一个顺序线性,设每个节点需占m个单元,若第0个元素的地址为addr,则第i个节点的地址为 addr+m*i