目录
1. 顺序表
1.1顺序表的结构
顺序表是一种线性表,是n个具有相同特性的数据元素的有限序列,呈现出一条线性,常见的线性表有,顺序表,链表,栈,队列,字符串,本质上都是数组,但是在数组上区分了静态的与动态的,而且是挨着存放的。顺序表本质上就是一个数组,在数组的基础上,要求是连续存储的,不能跳跃存储。
1.2 顺序表的实现
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLDatatype;
//顺序表结构的定义
typedef struct SeqList
{
SLDatatype* a;
int size;//顺序表储存的数据数量
int capacity;//顺序表的实际大小
}SL;
void SLInit(SL* ps);//顺序表的初始化
void SLPushBack(SL* ps, SLDatatype x); //尾插
void SLPopBack(SL* ps); //尾删
void SLPushFront(SL* ps, SLDatatype x); //头插
void SLPopFront(SL* ps); //头删
void SLPrint(SL* ps); //打印
void SLDestory(SL* ps); //销毁
void SLCheckCapacity(SL* ps); //检查容量-扩容
void SLInsert(SL* ps, int pos, SLDatatype x);//任意位置插入
void SLErase(SL* ps, int pos); //任意位置删除
int SLFind(SL* ps, SLDatatype x); //查找
void SLModify(SL* ps,int pos, SLDatatype x); //任意位置修改
顺序表的接口函数实现
#include "SeqList.h"
//初始化
void SLInit(SL* ps)
{
ps->a= NULL;
ps->size = ps->capacity= 0;
}
//检查——扩容
void SLCheckCapacity(SL* ps)
{
if (ps->size == ps->capacity)
{
int newcapacitv = ps->capacity == 0 ? 4 : ps->capacity * 2;
SLDatatype* tmp = (SLDatatype*)realloc(ps->a, newcapacitv * sizeof(SLDatatype));
if (tmp == NULL)
{
perror("relloc:");
exit(-1);//退出程序
}
ps->a = tmp;
ps->capacity = newcapacitv;
}
}
//尾插
void SLPushBack(SL* ps, SLDatatype x)
{
SLCheckCapacity(ps);//先检查内存
ps -> a[ps->size] = x;
ps->size++;
}
//打印
void SLPrint(SL* ps)
{
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
//销毁
void SLDestory(SL* ps)//
{ //
if (ps->a)
{
free(ps->a);
ps->a = NULL;
ps->size = ps->capacity = 0;
}
}
//尾删
void SLPopBack(SL* ps)
{
assert(ps->size);
ps->size--;
}
//头插
void SLPushFront(SL* ps, SLDatatype x)
{
SLCheckCapacity(ps);
int end = ps->size - 1;
while (end >= 0)
{
ps->a[end + 1] = ps->a[end];
end--;
}
ps->a[0] = x;
ps ->size++;
}
//头删
void SLPopFront(SL* ps)
{
assert(ps->size);
int begin = 1;
while (begin < ps->size)
{
ps->a[begin-1] = ps->a[begin];
begin++;
}
ps->size--;
}
//任意位置插入
void SLInsert(SL* ps, int pos, SLDatatype x)
{
assert(ps);
//检查ps
assert(pos >= 0 && pos <=ps->size);
SLCheckCapacity(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 >= 0 && pos < ps->size);
//检查边界
/*int begin = pos;
while (begin < ps->size - 1)
{
ps->a[begin] = ps->a[begin + 1];
begin++;
}*/
int begin = pos + 1;
while (begin < ps->size)
{
ps->a[begin - 1] = ps->a[begin];
begin++;
}
ps->size--;
}
//查找
int SLFind(SL* ps, SLDatatype x)
{
assert(ps);
for (int i = 0; i <= ps->size; i++)
{
if (ps->a[i] == x)
return i;
}
return -1;
}
//修改
void SLModify(SL* ps, int pos, SLDatatype x)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
ps->a[pos] = x;
}
在实际使用中,顺序表是存在很大缺陷的,如果空间不够了,就要增容,增容是需要代价的,realloc原地扩容,异地扩容,原地扩容返回的指针是不会变的,假设原地没有足够大的空间,异地扩容会返回另一个大空间的指针。这个异地扩容,不仅要寻找大空间,还要拷贝数据,还要释放旧内存。而且为了避免频繁扩容。我们扩容基本都是扩大二倍,可能会导致一定空间的浪费。而且,顺序表是要求从开始位置连续存储,在中间位置动手,就要频繁挪动数据,所以,我们就需要链表。
2. 链表
链表针对顺序表的缺陷,每一个区域存数值,并且存储下一个区域的地址,以便能够找到下一个区域。
链表实现时的结构
在操作中,我们通常都是直接操控指针了。
2.1链表的分类
链表有8个分类。
2.1.1关于单向与双向
链表中只储存下一个结构体的地址叫做单向,储存了上一个与下一个结构体的地址叫做双向。
2.1.2关于循环与非循环
尾节点链接空指针就是非循环,尾节点的next连接上头节点,我们称之为循环链表。
2.1.3关于带头或者不带头
关于不带头链表,就是一个普普通通的链表,每一个节点都储存数据,和下一个节点的数据。
带头链表呢,就是一个拥有“哨兵位”的链表。这个链表的头节点不储存有效数据,像是在站岗一样,带着下一个节点的地址。这与不带头节点的链表有什么区别呢?
不带头链表的头节点指针要指向第一个数据,如果我要头插,我需要改变这个头节点指针指向的位置,在头部插入一个新的节点,那么头节点就要指向这个新的头,我要改变这个指针指向的方向,所以,我要传参这个指针的地址,也就是二级指针。
但是,如果我带上一个哨兵位,这个位置的结构体不储存任何数据,从而使这个节点永远是我的头节点,致使头节点指针指向的结构体永远不发生改变,所以不需要传二级指针。
我们分别实现一个单项不带头非循环链表和一个双向带头循环链表。
2.2单项不带头非循环链表的实现
#pragma once
#include <stdio.h>
#include <stdlib.h>
typedef int SLDataType;
typedef struct SListNode
{
SLDataType data;
struct SListNode* next;
}SLTNode;
SLTNode* BuylistNode(SLDataType x);
void SListPrint(SLTNode* phead);
void SListInit(SLTNode* phead);
void SListPushBack(SLTNode** pphead,SLDataType x);
void SListPopBack(SLTNode** pphead);
void SListPushFront(SLTNode** pphead,SLDataType x);
void SListPopFront(SLTNode** pphead);
SLTNode* SListFind(SLTNode* phead,SLDataType x);
void SListInsert(SLTNode** pphead,int pos,SLDataType x);
void SListEase(SLTNode** pphead,int pos,SLDataType x);
void SListDestory(SLTNode** pphead);
void SListM(SLTNode** pphead,int pos,SLDataType );
#include "SList.h"
//新建链表
SLTNode* BuylistNode(SLDataType x)
{
SLTNode* newnode =(SLTNode*)malloc(sizeof(SLTNode));
newnode->data=x;
newnode->next=NULL;
return newnode;
}
void SListPrint(SLTNode* phead)
{
SLTNode* cur=phead;
while(cur!=NULL)
{
printf("%d->",cur->data);
cur=cur->next;
}
printf("NULL");
}
void SListPushBack(SLTNode** pphead,SLDataType x)
{
SLTNode* newnode= BuylistNode(x);
if (*pphead==NULL)
{
*pphead=newnode;
}
else
{
SLTNode* tail=*pphead;
while(tail->next!=NULL)
{
tail=tail->next;
}
tail->next = newnode;
}
}
void SListPopBack(SLTNode** pphead)
{
SLTNode* tailFront=NULL;
SLTNode* tail=*pphead;
while(tail->next!=NULL)
{
tailFront=tail;
tail=tail->next;
}
free(tail);
tail=NULL;
tailFront->next=NULL;
}
void SListPushFront(SLTNode** pphead,SLDataType x)
{
SLTNode* newnode= BuylistNode(x);
if(*pphead==NULL)
{
*pphead=newnode;
}
else
{
newnode->next=(*pphead);
*pphead=newnode;
}
}
void SListPopFront(SLTNode** pphead)
{
if(*pphead==NULL)
{
return;
}
else
{
SLTNode* prev=(*pphead)->next;
free(*pphead);
*pphead=prev;
}
}
SLTNode* SListFind(SLTNode* phead,SLDataType x)
{
SLTNode* cur=phead;
while(cur)
{
if((cur->data)==x) return cur;
else
{
cur=cur->next;
}
}
return NULL;
}
//任意位置插入
void SListInsert(SLTNode** pphead,int pos,SLDataType x)
{
SLTNode* newcode=BuylistNode(x);
if(pos-1<=0)
{
SListPushFront(&*pphead,x);
}
else
{
SLTNode* pre=*pphead;
SLTNode* Front_pre=*pphead;
for (int i = 0; i < pos; i++)
{
Front_pre=pre;
pre=pre->next;
}
Front_pre->next=newcode;
newcode->next=pre;
}
}
//任意位置修改
void SListEase(SLTNode** pphead,int pos,SLDataType x)
{
SLTNode* cur = BuylistNode(x);
SLTNode* pre = *pphead;
SLTNode* Front_pre=*pphead;
for(int i=0;i<pos-1;i++)
{
Front_pre= pre;
pre = pre->next;
}
cur->next=pre->next;
Front_pre->next=cur;
free(pre);
}
void SListDestory(SLTNode** pphead)
{
SLTNode* cur=*pphead;
while(cur)
{
SLTNode* next=cur->next;
free(cur);
cur=next;
}
}
void SlistDelete(SLTNode** pphead,int pos)
{
if(*pphead==NULL)
{
printf("该链表为空,无法删除");
exit(-1);
}
SLTNode* prev=*pphead;
SLTNode* Front_prev=*pphead;
for(int i=0;i<pos-1;i++)
{
Front_prev=prev;
prev=prev->next;
}
Front_prev->next=prev->next;
free(prev);
}
2.3双向带头循环链表的实现
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}LTNode;
LTNode* ListInit();
void ListPushBack(LTNode* phead,LTDataType x);
void ListPopBack(LTNode* phead);
void ListPushFront(LTNode* phead,LTDataType x);
void ListPopFront(LTNode* phead);
void ListEarse(LTNode* phead,int pos,LTDataType x);
void ListFind(LTNode* phead,LTDataType x);
void ListInsert(LTNode* phead,int pos,LTDataType x);
#include "List.h"
/*哨兵位不需要数值
也正是因为哨兵位没有具体的数值
不需要再对链表做调整的时候修改
使得plist所指向的位置永远不用发生改变
从而不对phead指针做调整
只调整其后的结构体
因而不用传二级指针*/
LTNode* ListInit()
{
LTNode* phead=(LTNode*)malloc(sizeof(LTNode));
phead->next=phead;
phead->prev=phead;
return phead;
}
void Listprint(LTNode* phead)
{
assert(phead);
LTNode* cur=phead->next;
while(cur!=phead)
{
printf("%d-",cur->data);
cur=cur->next;
}
printf("\n");
}
LTNode* Buy_new_list_node(LTDataType x)
{
LTNode* newnode=(LTNode*)malloc(sizeof(LTNode));
newnode->data=x;
return newnode;
}
void ListPushBack(LTNode* phead,LTDataType x)
{
assert(phead);
LTNode* tail=phead->prev;
LTNode* newnode=Buy_new_list_node(x);
//链接旧的尾部与新的尾部
tail->next=newnode;
newnode->prev=tail;
//链接新的尾部与头节点
newnode->next=phead;
phead->prev=newnode;
}
void ListPopBack(LTNode* phead)
{
LTNode* cur=NULL;
LTNode* Front_cur=NULL;
cur=phead->prev;
Front_cur=phead->prev->prev;
phead->prev=Front_cur;
Front_cur->next=phead;
free(cur);
}
void ListPushFront(LTNode* phead,LTDataType x)
{
assert(phead);
LTNode* newnode=Buy_new_list_node(x);
LTNode* cur=NULL;
cur=phead->next;
cur->prev=newnode; newnode->next=cur;
newnode->prev=phead; phead->next=newnode;
}
void ListPopFront(LTNode* phead)
{
assert(phead);
LTNode* head=phead->next;
phead->next=head->next;
head->next->prev=phead;
free(head);
}
void ListEarse(LTNode* phead,int pos,LTDataType x)
{
LTNode* cur=phead->next;
LTNode* newnode=Buy_new_list_node(x);
for (int i = 0; i < pos; i++)
{
cur=cur->next;
}
cur->next->prev=newnode; newnode->next=cur->next;
cur->prev->next=newnode; newnode->prev=cur->prev;
free(cur);
}
void ListFind(LTNode* phead,LTDataType x)
{
assert(phead);
LTNode* cur=phead->next;
int i;
for (i = 1;cur->data!=x; i++)
{
if(cur==phead)
{
printf("找不到\n");
break;
}
cur=cur->next;
}
printf("找到了,在第%d个",i);
}
void ListInsert(LTNode* phead,int pos,LTDataType x)
{
LTNode* newnode=Buy_new_list_node(x);
LTNode* cur=phead->next;
for (int i = 0; i < pos; i++)
{
cur=cur->next;
}
cur->prev->next=newnode; newnode->prev=cur->prev;
newnode->next=cur; cur->prev=newnode;
}
2.4顺序表与链表的优缺点
顺序表与链表
这两个结构各有优势,很难说谁更加优秀。
这是两个相辅相成的结构
顺序表
优点:
-
支持随机访问。需要随机访问支持算法可以很好的适用。
-
cpu高速缓存的利用率更高。
主存之上都是带电储存。
缺点:
-
头部中部插入删除时间效率低。
-
连续的物理空间,空间不够了要扩容,扩容有一定程度的消耗。而且可能存在空间浪费。
链表
优点:
-
任意位置的插入删除效率高。
-
按需申请释放空间。
缺点:
-
不支持随机访问。(用下标访问)意味着:一些排序,二分查找不适用。
关于顺序表在内存上的优势,具体可以在coolshell.cn/去查看 与程序员有关的CPU缓存知识
3.栈
3.1 栈的概念与结构
栈是一种特殊的线性表,其中允许在固定的一段进行插入和删除元素操作。进行数据插入和删除的操作的一段称为栈顶,另一端称为栈底。栈中的数据元素遵循着后进先出LIFO(Last In Frist Out)的原则。
压栈:站的插入操作叫做进栈/压栈/入栈,入栈元素在栈顶。
出栈,站的删除操作就叫做出栈。出数据也在栈顶。
栈只能在栈一端进入与输出,简单点讲,栈就好像一个圆形的桶,栈中的数据就像是圆盘,进入的事后从最顶上进入,出去的时候也是从最顶上输出。也像弹夹一样。
要想实现栈,可以用两种方式来实现。可以用数组来实现,也可以用链表来实现。
如果要使用链表栈,一般我们推荐要用头作栈顶,因为链表的头插与头删效率都是比在尾部删除插入要高的。并且设计成双向链表,否则删除数据效率低。
那么哪个结构更好呢?
显而易见应该是数组栈稍微好一点。
数组的缺点无非就是比链表对内存的要求更高一点,但除非是在非常需要节省的结构上,比如嵌入式,或者物联网。
在学会顺序表与链表之后,实现栈就变得轻而易举。下面我们来对顺序数组栈进行实现。
3.2 栈的实现
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
void StackInit(ST* ps);
void StackDestroy(ST* ps);
void StackPush(ST* ps,STDataType x);
void StackPop(ST* ps);
STDataType StackTop(ST* ps);
int StackSize(ST* ps);
bool StackEmpty(ST* ps);
#include "Stack.h"
/*
初始化的时候,top给的是0,意味着top指向栈顶数据的下一个
如果给的是-1,意味着top指向栈顶数据
当top是0的时候,位于数组的最前方,先给值,之后++
top会停在数组栈顶元素的下一个
因为下一步top就要插入值,所以top在栈顶数据的下一个
当top是-1的时候,插入数据时,就会先++top,再插入数据,
此时top指向的就是栈顶数据
因为下一步要++,再插入数据,所以top指向的是栈顶数据
*/
//初始化栈
void StackInit(ST* ps)
{
assert(ps);
ps->a=NULL;
ps->top=0;
ps->capacity=0;
}
//栈扩容
void StackDilatation(ST* ps)
{
if (ps->top == ps->capacity)
{
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
STDataType* tmp = realloc(ps->a,sizeof(STDataType)*newcapacity);
if (tmp == NULL)
{
perror("relloc fail");
exit(-1);//退出程序
}
ps->a=tmp;
ps->capacity = newcapacity;
}
}
//入栈
void StackPush(ST *ps, STDataType x)
{
assert(ps);
StackDilatation(ps);
ps->a[ps->top]=x;
ps->top++;
}
//出栈
void StackPop(ST* ps)
{
assert(ps);
assert(ps->top);
ps->top--;
}
//打印
void StackPrint(ST* ps)
{
for (int i = 0; i < ps->top; i++)
{
printf("%d-",ps->a[i]);
}
printf("\n");
}
//销毁栈
void StackDestroy(ST* ps)
{
assert(ps);
if (ps->a)
{
free(ps->a);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
}
//站内查找
void StackFind(ST* ps,STDataType x)
{
int i=0;
for (i = 0;i< ps->top; i++)
{
if(ps->a[i]==x)
{
printf("在第%d个位置\n",i+1);
break;
}
}
printf("没找到\n");
}
//栈顶元素
STDataType StackTop(ST* ps)
{
assert(ps);
return ps->a[ps->top-1];
}
//栈的大小
int StackSize(ST* ps)
{
assert(ps);
return ps->top;
}
//判断栈是否为空
bool StackEmpty(ST* ps)
{
assert(ps);
return ps->top==0;
}
4. 队列
4.1 队列的结构与概念
队列只允许在一段进行插入数据操作,在另一端惊醒删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)
入队列:进行插入操作的一段称为队尾
出队列:进行删除操作的一段称为队头
4.2队列的实现
队列的实现也可以用数组与链表这两种方式,相较于栈的实现,队列的实现使用链表要更优,因为如果使用队列结构,就要在头上出数据,如果用数组的话,频繁的挪动数据,会使效率非常低下。
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int QDataType;
typedef struct QueueNode
{
struct QueueNode* next;
QDataType data;
}QNode;
/*这里将原有的指针换成结构体
能够方便我们找头找尾
而且函数传参也不需要二级指针了*/
typedef struct Queue
{
QNode* head;
QNode* tail;
}Queue;
void QueueInit(Queue* pq);
void QueueDestory(Queue* pq);
void QueuePush(Queue* pq,QDataType x);
void QueuePop(Queue* pq);
QDataType QueueFront(Queue* pq);
QDataType QueueBack(Queue* pq);
int QueueSize(Queue* pq);
bool QueueEmpty(Queue* pq);
#include "Queue.h"
void QueueInit(Queue* pq)
{
assert(pq);
pq->head=NULL;
pq->tail=NULL;
}
void QueueDestory(Queue* pq)
{
assert(pq);
QNode* cur = pq->head;
while(cur!=NULL)
{
QNode* next=cur->next;
free(cur);
cur=next;
}
pq->head=pq->tail=NULL;
}
void QueuePush(Queue* pq,QDataType x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
newnode->data=x;
newnode->next=NULL;
if(pq->head==NULL)
{
pq->head=pq->tail=newnode;
}
else
{
pq->tail->next=newnode;
pq->tail=newnode;
}
}
void QueuePop(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
QNode* myhead=pq->head;
pq->head=pq->head->next;
free(myhead);
/*如果不加这个判断,就会导致当只有一个节点的时候
原来的head已经释放,但是tail没有
以后再访问tail的数据时,就会出现tail为野指针的情况*/
if(pq->head==NULL)
{
pq->tail=NULL;
}
}
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->head->data;
}
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->tail->data;
}
int QueueSize(Queue* pq)
{
assert(pq);
int n=0;
QNode* cur=pq->head;
while(cur)
{
++n;
cur=cur->next;
}
return n;
}
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->head==NULL;
}