大家好,我是集美貌与才华于一身的阿俊呐,咳咳咳…不接受任何反驳,感谢你辣么好看还来关注我,嘿嘿嘿,让我们进入正题叭…biu~ biu~…
这篇博客主要是我学完数据结构(严蔚敏版),想记录下来以后复习用。
- 从零搭建起栈和队列的操作
- 顺序动态栈和动态数组的关系(栈也可用链式结构实现,不过不常用)
- 链式队列和链表的关系(队列也可用顺式结构实现,不过不常用)
- 数组与链表的优缺点
数据结构设计思路剖析
先聊聊基础的顺序(动态数组)、链式结构(链表)的设计思路,再唠唠如何在已有基础上衍生出比较高级的顺序(动态顺序栈)、链式结构(链队)的设计
动态数组的数据结构
//动态数组的数据结构
typedef struct
{
int* elem;//数组首地址
int length;//数组长度
int listsize;//数组可用空间
}SqList;
- 动态数组克服了静态数组无法改变空间大小的缺陷
- 该数据结构也类似于面向对象思想,将动态数组看做一个对象,其中包含了三个属性,待会儿要实现的功能就是其操作
在动态数组的基础上添加限制,稍作变形,就是顺序动态栈了
顺序动态栈的数据结构
typedef struct
{
int* base;//栈底
int* top;//栈顶
int listsize;//可用空间大小
}SqStack;
看看动态栈是如何由动态数组衍生而来
- base相当于动态数组的首地址elem
- top指向最后一个有效元素的下一个位置(特性)
- top-base = 动态数组的length
- top = base 时栈空
链表的数据结构
typedef struct LNode//链表
{
int data;//节点中的数据
struct LNode* next;//指向下一个节点的指针
}LNode,*LinkList;
在链表的数据结构的构成中,可分为两大类:数据项,指针项
- 数据项可根据需要任意定义,灵活多变
- 指针项虽然永远只占4位,但也是可指向任意节点
在链表的基础上添加首尾指针,稍作限制,就是队列啦
链式队列的数据结构
typedef struct LNode//链表
{
int data;
struct LNode* next;
}LNode,*LinkList;
typedef struct
{
LNode* front;//队头指针
LNode* rear; //队尾指针
}LinkQueue;
看看队列是如何由链表衍生而来
- 只需添加指向节点的头尾指针即可,因为链表的特点是只需要知道头指针,就可遍历整个链表;为什么添加尾指针呢?因为队列要求一端进,一端出,所以得充分利用头和尾
功能构造思路
tips
数据结构的定义直接决定了其基本操作及算法实现的难度,所以呀,我们学习数据结构一是为了掌握基本的结构定义,二是体会各种结构是如何构造而来,今后遇到实际问题时方有可能设计出不错的数据结构
操作分为四个部分–增查改删
线性表无论是动态数组/栈还是链表/队列,基本操作均逃不出四座大山–增查改删,让我们来给他们排排序
- 增加元素永远是老大,因为最开始是0,什么都没有,我们需建立一个表,其余的操作方可进行,建立一个表也就是从空表开始反复增加(插入)元素。若是一个表都不存在,我们拿什么查改删
- 查找元素是老二,为啥把它排第二,我改删不服。若是把查找排老四,一进行改时突然发现,我该改谁呢?同时删也发现了这个问题,没有改删的对象可咋办,诶呀呀,查你还是快回第二吧。
- 改删元素位置顺序不太重要,看需要哪个操作多,相较之下,修改元素是比较简单的,找到元素后修改就行,而删除在数组中麻烦,在链表方便
以上的功能排序也是具体实现时的顺序,只要你掌握了动态数组和链表的增查改删,
栈功能(仅有增删,无查改)
- 入栈–增
- 出栈–删
队列功能(仅有增删,无查改)
- 入队–增
- 出队–删
二者由于自身特性限制,比动态数组和链表的功能还少,所以实现起来也较简单,只有两个功能。
一个有意思的偷懒
- 值得一提的是为啥我们选择在链表的头结点后删除,尾节点插入。
按理说无论是链表的头和尾,均可进行插入删除操作呀。那让我们来唠嗑唠嗑,假设在链表头结点插入,新节点指向头结点后一个元素,头结点指向新节点,插入成功;再看看在尾节点删除,删除得知道尾节点前一个节点,诶嘿,突然发现,单向链表只能往后找,没法回头呀,这时候如果想解决问题,仅有添加一个指向尾节点前一个的指针咯。若是选择在尾部插入,尾节点直接指向新节点就大功告成,头结点删除也很容易。相较之下,后者少用一个指针,更为简单,所以我们采用简单的方法
顺序结构和链式结构的优缺点
- 顺序结构便于查找,修改,只要寻址即可;而删除,增加需要移动位置,不便于实现
- 链式结构便于删除,增加,只需改变指针指向,且当节点信息很多时,链式效率极高;而对于查找,修改,必须从头到尾遍历
- 二者是互补关系,各有所长,应根据实际应用选择合适的结构
完整实现代码
栈实现完整代码
#include<iostream>
using namespace std;
#include<stdlib.h>
#define INIT_SIZE 100
#define INCREMENT 10
//top指向有元素的下一个位置
//top-base = 动态数组的length
//base相当于动态数组的首地址elem
typedef struct
{
int* top;
int* base;
int listsize;
}SqStack;
void InitStack(SqStack &S)
{
S.base = (int*)malloc(INIT_SIZE*sizeof(int));
S.top = S.base;
S.listsize = INIT_SIZE;
}
//入栈
void Push(SqStack &S,int e)
{
if(S.top - S.base >= S.listsize)//动态扩容
{
S.base = (int*)realloc(S.base,(S.listsize+INCREMENT)*sizeof(int));
S.top = S.base + S.listsize;//更新栈顶
S.listsize = S.listsize+INCREMENT;//更新可用空间
}
*S.top = e;
S.top++;
}
//空->true;非空->false
bool IsEmpty(SqStack S)
{
if(S.top == S.base)return true;
else return false;
}
//仅删除栈顶,不返回值
void Pop(SqStack &S)
{
if(!IsEmpty(S))
{
S.top--;//只删除栈顶,不弹出
}
else cout<<"栈空!"<<endl;
}
//仅返回栈顶值,不删除
int GetTop(SqStack S)
{
if(!IsEmpty(S))
{
return *(S.top-1);
}
else return -11111;//表示空栈
}
int main()
{
SqStack S;
InitStack(S);
for(int i =0; i <10; i++)
{
Push(S,i);
cout<<GetTop(S);
}
cout<<endl;
for(int i =0; i <10; i++)
{
cout<<GetTop(S);
Pop(S);
}cout<<GetTop(S);Pop(S);
return 0;
}
队列实现完整代码
#include<iostream>
using namespace std;
#include<stdlib.h>
//队列基本操作
typedef struct QNode
{
int data;
struct QNode* next;
}QNode,LNode,*LinkList;
typedef struct
{
QNode* front;
QNode* rear;
}LinkQueue;
void InitQueue(LinkQueue &Q)
{
Q.front = Q.rear = (QNode*)malloc(sizeof(QNode));//头尾均指向头指针
Q.front->next = NULL;//尾部赋空
}
//尾部插入
void Enqueue(LinkQueue &Q,int data)
{
QNode* p = (QNode*)malloc(sizeof(QNode));
p->data = data;
p->next = NULL;
Q.rear->next = p;
Q.rear = p;
}
//空->true;非空->false
bool IsEmpty(LinkQueue Q)
{
if(Q.front->next) return false;
else return true;
}
//头部删除;注意删除的节点为尾节点时,重新将尾节点指向头结点
void Dequeue(LinkQueue &Q)
{
if(!IsEmpty(Q))
{
Q.front->next = Q.front->next->next;
if(IsEmpty(Q)) Q.rear = Q.front;//删除尾节点,重新将尾指针指向头
}
}
//获取队头元素
int GetTop(LinkQueue &Q)
{
if(!IsEmpty(Q))
{
return Q.front->next->data;
}
else return -111111;
}
int main()
{//模仿栈测试,你写一个队列测试吧
return 0;
}