一 . 线性表
1.概念(顺序表的爸爸):具有一类相同特性的数据的集合:
线性表是n个具有相同特性的数据元素的有限序列。 线性表是⼀种在实际中⼴泛使 ⽤的数据结构,常⻅的线性表:顺序表、链表、栈、队列、字符串...线性表在逻辑上是线性结构,也就说是连续的⼀条直线。但是在物理结构上并不⼀定是连续的, 线性表在物理上存储时,通常以数组和链式结构的形式存储。
二 . 顺序表
1.顺序表的底层结构:
顺序表的底层结构是数组,所以顺序表在逻辑结构上是线性的,在物理结构上是线性的
2.概念:顺序表是⽤⼀段物理地址连续的存储单元依次存储数据元素的线性结构,⼀般情况下采⽤数组存储。因为顺序表的底层结构是数组,也就是说,顺序表是对数组的封装(结构体),实现了常⽤的增删改查等接⼝:
3.分类:
1)已知顺序表大小--静态顺序表(使用定长数组存储数据)
缺点:空间给少了不够用,给多了造成空间浪费
typedef int SLDataType;
#define N 7
typedef struct SeqList
{
SLDataType a[N]; ----定长数组
int size; ----有效数据个数
}SL;
2)不知道顺序表大小--动态顺序表
malloc:申请一块空间,且是连续的
realloc:在原有基础上继续增加空间
calloc:相比malloc会初始化
优点:按需申请
```
typedef struct SeqList
{
SLDataType* a;
int size; ----有效数据个数
int capacity; ----空间容量
}SL;
```
4.动态顺序的实现
```
SeqList.h ----头文件
typedef int SLDataType;
// 动态顺序表 -- 按需申请
typedef struct SeqList
{
SLDataType* a;
int size; // 有效数据个数
int capacity; // 空间容量
}SL;
//初始化和销毁
void SLInit(SL* ps);
void SLDestroy(SL* ps);
void SLPrint(SL* ps);
//扩容
void SLCheckCapacity(SL* ps);
//头部插⼊删除 / 尾部插⼊删除
void SLPushBack(SL* ps, SLDataType x);
void SLPopBack(SL* ps);
void SLPushFront(SL* ps, SLDataType x);
void SLPopFront(SL* ps);
//指定位置之前插⼊/删除数据
void SLInsert(SL* ps, int pos, SLDataType x);
void SLErase(SL* ps, int pos);
int SLFind(SL* ps, SLDataType x);
。SeqList.c 源文件
#include"SeqList.h"
//动态顺序表的初始化
void SLInit(SL* ps)
{
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
//动态顺序表的销毁
void SLDestroy(SL* ps)
{
if (ps->arr)
{
free(ps->arr);
}
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
void SLCheckCapacity(SL* ps)//判断空间是否充足,若不足,先增容,再插入
{
if (ps->size == ps->capacity)
{
//增容(一般是成倍数增加)
//若capacity为0.给个默认值,否则x2倍
int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
SLDatatype* tmp = (SLDatatype*)realloc(ps->arr, newCapacity * sizeof(SLDatatype));//申请的是newCapacity个 整型 字节大小的空间
if (tmp == NULL)//动态空间申请失败
{
perror("realloc fail");//打印的一个信息
exit(1);//不继续插入数据,exit(1)表示异常退出.这个1是返回给操作系统的。
}
ps->arr = tmp;
ps->capacity = newCapacity;
}
}
void SLPushBack(SL* ps, SLDatatype x) //尾插
{
//防止传入空的顺序表
//粗暴的解决方法
assert(ps); //表达式为真,可以继续进行,等价于ps!=NULL
///温柔的方式
//if (ps == null)
//{
// return;
//}
SLCheckCapacity(ps);
ps->arr[ps->size] = x;
ps->size++;
}
void SLPushFront(SL* ps, SLDatatype x) //头插
{
assert(ps);
SLCheckCapacity(ps);
//整体数据后移一位
for (int i = ps->size; i >0; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
//下标为0的位置空出来
ps->arr[0] = x;
ps->size++;
}
void SLInsert(SL* ps, SLDatatype x, int pos)//指定位置之前插入数据
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);//等于时,分别代表头插和尾插
SLCheckCapacity(ps);
//pos及之后的数据整体向后移动一位
for (int i = ps->size; i > pos ; i--)
{
ps->arr[i] = ps->arr[i - 1];//最后一次移动是 pos+1=pos
}
ps->arr[pos] = x;
ps->size++;
}
void SLPopBack(SL* ps)//尾删
{
assert(ps);
assert(ps->size);
//ps->arr[ps->size - 1] = -1; //多余的。可要可不要
ps->size--;
}
void SLPopFront(SL* ps) //头删
{
assert(ps);
assert(ps->size);
//数据整体向前挪动一位
for (int i = 0; i < ps->size-1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
void SLErase(SL* ps, int pos)//删除指定位置的数据
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
//pos之后的数据整体向前挪动一位
for (int i = pos; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->size--;
}
int SLFind(SL* ps, SLDatatype x) //查找
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
if (ps->arr[i] == x)
{
return i;
}
}
//没有找到,返回无效下标
return -1;
}
void SLPrint(SL* ps)
{
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->arr[i]);
}
printf("\n");
}
5.顺序表算法题
1)移除元素
https://leetcode.cn/problems/remove-element/description/
```
int removeElement(int* nums, int numsSize, int val)
{
int a=0;
int b=0;
while(a<numsSize)
{
if(nums[a]==val)
{
a++;
}
else
{
nums[b]=nums[a];
a++;
b++;
}
} c
return b;
}
```
2)删除有序数组中的重复项
https://leetcode.cn/problems/remove-duplicates-from-sorted-array/description/
```
int removeDuplicates(int* nums, int numsSize)
{
int a=0;
int b=a+1;
while(b<numsSize)
{
if(nums[a]==nums[b])
{
b++;
}
else
{
a++;
nums[a]=nums[b];
b++;
}
}
return ++a;
}
```
3)合并两个有序数组
https://leetcode.cn/problems/merge-sorted-array/description/
```
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n)
{
int a=m-1;
int b=n-1;
int c=m+n-1;
while(a>=0 && b>=0)
{
if(nums1[a]>nums2[b])
{
nums1[c]=nums1[a];
a--;
c--;
}
else
{
nums1[c]=nums2[b];
b--;
c--;
}
}
while(b>=0)
{
nums1[c--]=nums2[b--];
}
}
三 .单链表
1.概念与结构:
原名:不带头单向不循环链表
链表是一种物理存储结构上非连续性,非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。逻辑结构是线性的,物理结构不一定是线性的。
2.结点:
链表由一个个结点组成,而结点就是一个结构体,定义链表就是定义结点。
结点的组成主要有两个部分:当前结点要保存的数据和保存下⼀个结点的地址(指针变量)。结点与结点之间不一定是连续的
指针变量plist保存的是第⼀个结点的地址,我们称plist此时“指向”第⼀个结点,如果我们希望plist“指向”第⼆个结点时,只需要修改plist保存的内容为0x0012FFA0。链表中每个结点都是独⽴申请的(即需要插⼊数据时才去申请⼀块结点的空间),我们需要通过指针变量来保存下⼀个结点位置才能从当前结点找到下⼀个结点。
3.链表的性质
1)、链式机构在逻辑上是连续的,在物理结构上不⼀定连续
2)、结点⼀般是从堆上申请的
3)、从堆上申请来的空间,是按照⼀定策略分配出来的,每次申请的空间可能连续,可能不连续
4)、当我们想要保存⼀个整型数据时,实际是向操作系统申请了⼀块内存,这个内存不仅要保存整型数据,也需要保存下⼀个结点的地址(当下⼀个结点为空时保存的地址为空)。当我们想要从第⼀个结点⾛到最后⼀个结点时,只需要在当前结点拿上下⼀个结点的地址就可以了。
5)在链表中,没有增容的概念,需插入新的数据,直接申请一个结点大小的空间就可以了。
4.单链表结构的定义
.h 头文件
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//链表由一个一个的结点组成,因此定义链表就是定义结点,而一个结点有两个部分组成:存储的数据和指针(指向下一个结点的地址)
//定义链表(结点)的结构
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SLTNode;
//链表的打印
void SLTPrint(SLTNode* phead); //phead指向node1
//插入数据
//二级指针
void SLPushBack(SLTNode** pphead,SLTDataType x);//尾插
void SLPushFront(SLTNode** pphead,SLTDataType x);//头插
void SLTInsert(SLTNode** pphead, SLTNode pos, SLTDataType x);//在指定位置之前插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);//在指定位置之后插入数据
//删除数据
void SLPopBack(SLTNode** pphead); //尾删
void SLPopFront(SLTNode** pphead); //头删
void SLTErase(SLTNode** pphead, SLTNode* pos);//删除pos结点
void SLTEraseAfter(SLTNode* pos);//删除pos之后的结点
//销毁链表
void SListDestroy(SLTNode** pphead);
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
.c 源文件
#include"SList.h"
void SLTPrint(SLTNode* phead) //phear指向链表的第一个结点的地址
{
SLTNode* pcur = phead; //pcur指向的是第一个结点的指针
while (pcur)
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
//定义一个申请新结点的函数
SLTNode* SLTBuyNode(SLTDataType x)
{
SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));
if (node == NULL)
{
perror("malloc fail");
exit(1);
}
node->data = x;
node->next = NULL;
return node;
}
//尾部插入函数 //二级指针
void SLPushBack(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
//pphead接收的是&plist(plist就是指向第一个节点的指针)
// *pphead=plist(解引用pphead)
//申请新结点
SLTNode* newnode = SLTBuyNode(x);
//用链表尾结点指向定义的新结点从而来插入数据
//找尾结点
SLTNode* pcur = *pphead; //此时pcur和phear都为NULL
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
while (pcur->next)
{
pcur = pcur->next;//pcur下一个指向地址给当前pcur指针;
}
//连接pucr和newnode
pcur->next = newnode;
}
}
//头部插入函数
void SLPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = SLTBuyNode(x);
//让新结点newnode的next指针 指向头结点*pphead
newnode->next = *pphead;
*pphead = newnode;
}
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead);
assert(pos);
if (pos == *pphead)
{
SLPushFront(pphead, x);
}
else
{
SLTNode* newnode = SLTBuyNode(x);
SLTNode* prev = *pphead; //找pos的前一个结点prev(需要从头开始遍历)
while (prev->next != pos)
{
prev = prev->next;//这里是继续循环的操作,prev要一个一个往下找
}
//以上操作已经找到了prev和pos,现在需要在二者中间插入newnode
newnode->next = pos->next;
prev->next = newnode;
}
}
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
//要先让新插入的结点newnode指向指定位置的下一个结点pos->next,再让pos->next指针指向newnode
assert(pos);
SLTNode* newnode = SLTBuyNode(x);
newnode = pos->next;
pos->next = newnode;
}
//删除数据
//删除pos结点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead && *pphead);
assert(pos);
//pos为头结点时,要头删
if (pos == *pphead)
{
SLPopFront(pphead);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
//删除pos之后的结点
void SLTEraseAfter(SLTNode* pos)
{
assert(pos && pos->next);
SLTNode* del = pos->next;//这里为什么要保存pos->next结点?记得看回放
pos->next = pos->next->next;
free(del);
del = NULL;
}
//尾部删除数据
void SLPopBack(SLTNode** pphead)
{
assert(pphead && *pphead); //pphead意味着传的参数不为空,*pphead表示链表不为空
//处理只有一个结点的情况:要删除的就是头结点
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
//找尾结点的前一个prev和尾结点ptail
SLTNode* prev = NULL;
SLTNode* ptail = *pphead;
while (ptail->next)
{
prev = ptail;
ptail = ptail->next;
}
prev->next = NULL;
free(ptail);
ptail = NULL;
}
}
//头部删除数据
void SLPopFront(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
//销毁链表
void SListDestroy(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* pcur = *pphead;
while (pcur)
{
SLTNode* next = pcur->next;
free(pcur);
pcur = next;
}
*pphead = NULL;
}
//查找数据
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
assert(phead);
SLTNode* pcur = phead;
while (pcur)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
//没有找到
return NULL;
}
5.练习:
1)移除链表元素(OJ平台203)
思路:创建新链表,遍历原链表,找出不为val的结点,往新链表尾插
```
/*
Definition for singly-linked list.
struct ListNode
{
* int val;
* struct ListNode *next;
};
*/
typedef struct ListNode LN;
struct ListNode* removeElements(struct ListNode* head, int val) {
//创建新链表
LN* newHead,*newTail;
newHead=newTail=NULL;
//遍历原链表
LN* pcur=head;
while(pcur)
{
if(pcur->val!=val)
{
//链表为空
if(newHead==NULL)
{
newHead=newTail=pcur;
}
else
{
newTail->next=pcur;
newTail=newTail->next;
}
}
pcur=pcur->next;
}
//跳出循环
if(newTail)
{
newTail->next=NULL;
}
return newHead;
}
```
2)反转链表(OJ平台206)
```
/*
Definition for singly-linked list.
struct ListNode
{
* int val;
* struct ListNode *next;
};
*/
typedef struct ListNode ln;
struct ListNode* reverseList(struct ListNode* head)
{
//处理空链表,(head 为空链表的情况)
if(head==NULL)
{
return head;
}
//创建三个指针,来修改指针指向
ln* l1,*l2,*l3;
l1=NULL,l2=head,l3=l2->next;
while(l2)
{
l2->next=l1;
//l1走到l2,l2走到l3
l1=l2;
l2=l3;
if(l3)
{
//l3走到l3的下一个结点
l3=l3->next;
}
}
//此时l1就是链表反转后的头结点
return l1;
}
```
3)找到链表中间结点(OJ平台876)
思路:快慢指针,慢指针每次走一步,快指针每次走两步
```
/**
Definition for singly-linked list.
struct ListNode
{
int val;
struct ListNode *next;
};
*/
typedef struct ListNode ln;
struct ListNode* middleNode(struct ListNode* head)
{
ln*slow=head;
ln*fast=head;
while(fast && fast->next)
{
slow=slow->next;
fast=fast->next->next;
}
return slow;
}
```
4)合并两个有序列表(OJ平台21)
思路:创建一个非空新链表,遍历原链表,比较大小,谁小尾插到新链表中
```
/**
Definition for singly-linked list.
struct ListNode
{
int val;
struct ListNode next;
};
*/
typedef struct ListNode ln;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
//处理两个原链表有为空链表的情况
if(list1==NULL)
{
return list2;
}
if(list2==NULL)
{
return list1;
}
//创建新链表
ln*newHead,*newTail;
newHead=newTail=(ln*)malloc(sizeof(ln));
//创建两个指针,分别指向两个链表头结点
ln* n1=list1;
ln* n2=list2;
while(n1 && n2)
{
if(n1->val<n2->val)
{
newTail->next=n1;
newTail=newTail->next;
n1=n1->next;
}
else
{
newTail->next=n2;
newTail=newTail->next;
n2=n2->next;
}
}
//跳出循环,要么n1为空,要么n2为空
if(n1)//n2为空
{
newTail->next=n1;
}
if(n2)
{
newTail->next=n2;
}
ln* ret=newHead->next;
free(newHead);
newHead=NULL;
return ret;
}
```
5)链表分割(牛客网)
思路:创建两个非空链表,大链表和小链表,遍历原链表,小于x的尾插到小链表,大于x的创建大新链表,大小链表首尾相连(要释放大链表尾结点的next指针为空,否则陷入死循环)
```
/*
struct ListNode
{
int val;
struct ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};*/
#include <cstdlib>
class Partition {
public:
ListNode* partition(ListNode* pHead, int x)
{
// write code here
//创建两个非空链表,大链表和小链表
ListNode* lessHead,*lessTail;
lessHead=lessTail(ListNode*)malloc(sizeof(ListNode));
ListNode* bigHead,*bigTail;
bigHead=bigTail=(ListNode*)malloc(sizeof(ListNode));
//遍历链表小于x放入小链表,其它到大链表
ListNode* pcur=pHead;
while(pcur)
{
if(pcur->val<x)
{
lessTail->next=pcur;
lessTail=lessTail->next;
}
else
{
bigTail->next=pcur;
bigTail=bigTail->next;
}
pcur=pcur->next;
}
//大链表尾结点next指针置为空
bigTail->next=NULL;
//大小链表相连
lessTail->next=bigHead->next;
ListNode* ret=lessHead->next;
free(lessHead);
free(bigHead);
lessHead=bigHead=NULL;
return ret;
}
};
```
6)链表的回文结构(牛客网)
思路1:创建一个新数组,遍历原链表,将链表结点中的值放入数组,在数组中判断是否为回文结构
思路2:用快慢指针找出中间结点,将中间结点之后的链表进行反转,与中间结点之前的链表进行比较
```
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};*/
class PalindromeList {
public:
ListNode* findcenternode(ListNode* phead)
{
ListNode* fast=phead;
ListNode* slow=phead;
while(fast && fast->next)
{
slow=slow->next;
fast=fast->next->next;
}
return slow;
}
ListNode* reverseList(ListNode* phead)
{
ListNode* n1,*n2,*n3;
n1=NULL;
n2=phead;
n3=n2->next;
while(n2)
{
n2->next=n1;
n1=n2;
n2=n3;
if(n3)
{
n3=n3->next;
}
}
return n1;
}
bool chkPalindrome(ListNode* A) {
//找中间结点
ListNode* mid=findcenternode(A);
//反转中间结点之后的链表
ListNode* right=reverseList(mid);
//从原链表和反转链表比较结点值
ListNode* left=A;
while(right)
{
if(left->val!=right->val)
{
return false;
}
left=left->next;
right=right->next;
}
return true;
}
};
```
7)相交链表(OJ平台160)
思路:找两个链表的结点数差值,长链表先走差值步,使两个链表在同一起跑线,两个链表开始遍历,比较是否为同一个结点
```
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
ListNode* n1=headA;
ListNode* n2=headB;
int sizeA=0;
int sizeB=0;
while(n1)
{
sizeA++;
n1=n1->next;
}
while(n2)
{
sizeB++;
n2=n2->next;
}
//求绝对值
int gap=abs(sizeA-sizeB);
//长链表先走gap步,让他们在同一起点
ListNode* longlist=headA;
ListNode* lesslist=headB;
if(sizeA<sizeB)
{
longlist=headB;
lesslist=headA;
}
while(gap--)
{
longlist=longlist->next;
}
//已在同一起点,开始遍历两个链表,看是否相等
while(lesslist && longlist)
{
if(longlist == lesslist)
{
return longlist; //return lesslist
}
longlist=longlist->next;
lesslist=lesslist->next;
}
return NULL;
}
```
8)环形链表(OJ平台141)
思路:快慢指针
```
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
bool hasCycle(struct ListNode *head) {
ListNode* less =head;
ListNode* fast =head;
while(fast && fast->next)
{
fast=fast->next->next;
less=less->next;
if(fast==less)
{
return true;
}
}
return false;
}
```
9)环形链表Ⅱ(OJ平台142)
思路:用快慢指针找到相遇点,在相遇点和头结点开始遍历链表,在相遇位置就是入环的第一个结点
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
struct ListNode *detectCycle(struct ListNode *head) {
ListNode* less=head;
ListNode* fast=head;
while(fast && fast->next)
{
less=less->next;
fast=fast->next->next;
if(fast==less)
{
ListNode* mid=fast;
ListNode* pcur=head;
while(pcur!=mid)
{
pcur=pcur->next;
mid=mid->next;
}
return pcur;
}
}
//链表不带环
return NULL;
}
```
10)随即链表的复制(OJ平台138)
思路:在原链表的基础上继续复制链表(用尾插),通过原链表,在复制的链表中置random指针,复制链表和原链表断开
```
/**
* Definition for a Node.
* struct Node {
* int val;
* struct Node *next;
* struct Node *random;
* };
*/
typedef struct Node Node;
Node* buyNode(int x)
{
Node* newnode=(Node*)malloc(sizeof(Node));
newnode->val=x;
newnode->next=newnode->random=NULL;
return newnode;
}
//在原链表基础上复制
void Add(Node* phead)
{
Node* pcur=phead;
while(pcur)
{
Node* Next=pcur->next;
//创建新节点,尾插到pcur
Node* newnode=buyNode(pcur->val);
pcur->next=newnode;
newnode->next=Next;
pcur=Next;
}
}
struct Node* copyRandomList(struct Node* head) {
if(head==NULL)
{
return NULL;
}
//原链表复制结点
Add(head);
//置random结点
Node* pcur=head;
while(pcur)
{
Node* copy=pcur->next;
if(pcur->random!=NULL)
{
copy->random=pcur->random->next;
}
pcur=copy->next;
}
//断开链表
pcur=head;
Node* newHead,*newTail;
newHead=newTail=pcur->next;
while(pcur->next->next)
{
pcur=pcur->next->next;
newTail->next=pcur->next;
newTail=newTail->next;
}
return newHead;
}