一、栈
- 栈(stack)是限定仅在表尾进行插入和删除操作的线性表
- 栈的存储数据原则:后进先出
- 进栈:栈的插入操作,也称压栈、入栈
- 出栈:栈的删除操作,也称弹栈
- 允许插入和删除的一端称为栈顶(top),另一端称为栈底(botton),不含任何数据元素的栈称为空栈
顺序栈的基本操作C语言实现代码: - 顺序栈是的存储空间是连续且预先分配的
类型定义:
#define MAXLEN 100
typedef struct{
int data[MAXLEN];
int top;
}SeqStack;
初始化栈
void IniStack(SeqStack *S){
S->top=-1;
}
判断栈空
int EmptyStack(SeqStack *S){
if(S->top==-1) // 栈空
return q;
else
return 0;
}
判断栈满
int FullStack(SeqStack *S){
if(S->top==MAXLEN-1) // 栈满
return q;
else
return 0;
}
进栈
int PushStack(SeqStack *S,int x){
if(FullStack(S)){
printf("栈满,不能进栈!");
return 0;
}else{
S->top++;
S->data[S->top]=x;
return 1;
}
}
出栈
int PopStack(SeqStack *S,int x){
if(EmptyStack(S)){
printf("栈空,不能出栈!");
return 0;
}else{
*x=S->data[S->top];
S->top--;
return 1;
}
}
取栈顶元素
int GetTop(SeqStack *S,int x){
if(EmptyStack(S)){
printf("栈空,取栈顶元素失败!");
return 0;
}else{
*x=S->data[S->top];
return 1;
}
}
链栈的基本操作C语言实现代码:
类型定义:
#define MAXLEN 100
typedef struct stacknode{
int data;
struct stacknode *next;
}LinkStack;
初始化栈
LinkStack *IniStack(){
LinkStack *S
S=NULL;
return S; // 初始化栈为空
}
判断栈空
int EmptyStack(LinkStack *S){
if(S==NULL) // 栈空
return q;
else
return 0;
}
进栈
LinkStack *Push(LinkStack *S,int x){
LinkStack *p;
p=(LinkStack *)malloc(sizeof(LinkStack)); // 生成新结点
p->data=x;
p->next=S; // 将新结点插入链表表头之前
S=p; // 新结点作为栈顶
return S;
}
出栈
LinkStack *Pop(LinkStack *S,int x){
LinkStack *p;
if(EmptyStack(S)){
printf("栈空,不能出栈!");
return NULL;
}else{
*x=S->data;
p=S;
S=S->next;
free(p); // 释放原栈顶空间
return S;
}
}
取栈顶元素
int GetTop(LinkStack *S,int x){
if(EmptyStack(S)){
printf("栈空!");
return 0;
}else{
*x=S->data;
return 1;
}
}
数组模拟与STL容器 (C++)
1.新建⼀个栈
int stk[N], top = -1;//top指向栈顶。基底是0则top=-1,是1则top=0
stack<int>stk;
2.插⼊数据
stk[++ top] = x;//将x插⼊栈
stk.push(x);//将x⼊栈
3.删除栈顶元素
top--;//删去栈顶
stk.top();//删栈顶
4.查询栈顶数据
stk[top];
stk.top();//取栈顶
5.如何清空⼀个栈
5.1 依次判断top是否存在,并删除top。O(n)
5.2 直接将top指针指向基地址。O(1)
6.数组模拟和STL容器的区别:
6.1 STL⾥⾯的stack没有清空函数,只能⼀个⼀个出栈or新建⼀个栈。
6.2 数组模拟的栈可以O(1)取出从栈顶往下数第k个元素(stk[top - k + 1),⽽STL实现的栈不可以。
二、队列
- 队列按照先进先出的原则存储数据
- 队列是只允许在一端进行插入操作而在另一端进行删除操作的线性表
根据功能不同分为普通队列、循环队列和优先队列。实现⽅式各有不同可以通过数组模拟也可以通过结构体加指针来实现
普通队列(数组模拟)
1.初始化⼀个空队列 队列数组、队头、队尾。
队头:指向队列的第⼀个元素
队尾:指向队列的最后⼀个元素
int q[N], hh = 0, tt = -1;
2.可以进⾏的操作
2.1 判断队列是否为空
if(hh <= tt) 若队头⼩于等于队尾说明队列不空。
else 否则说明队尾在队头前⾯,此队列不存在说明队列为空。
2.2 ⼊队
q[++tt] = x; //将元素x⼊队
2.3 出队
hh++;//将队头出队,下⼀个元素成为队头
2.4 查询队列中的第k个元素(⽤STL容器实现的队列不具有操作4)
//⾸先需要确保队列含有第k个元素
if(q[hh + k - 1] <= tt) //如果第k个元素存在于队列当中的话
{ //输出第k个元素
cout << q[hh + k - 1] << endl;
}
循环队列(数组+STL)
/*
tips:如何计算队列中有多少元素?
如果hh <= tt 则⼀共 tt - hh + 1个元素。
如果tt < hh 则⼀共有tt + size - hh + 1个元素
*/
1.初始化⼀个空队列 队列数组、队头、队尾。
int q[size], hh = 1, tt = size;//tt = 0 也⾏ size要⼤于队列最⼤时的⻓度
//tips:如果刚好等于队列最⼤时的⻓度,可能在mod size之后tt + 1 == hh,
//此时你知道是刚好利⽤了全部空间还是空队列。
queue<int>q;
2.可以进⾏的操作
2.1 判断队列是否为空
if(hh != tt % size + 1) 说明队列不空。
else 说明队列为空。
if(q.empty()) 队列为空 //也可以⽤q.size() 判断
else 队列不为空
2.2 ⼊队
tt = tt % size + 1
q[tt] = x; //将元素x⼊队
2.3 出队
hh = hh % size + 1;//将队头出队,下⼀个元素成为队头
2.4 查询队列中的第k个元素(⽤STL容器实现的队列不具有操作4)
//⾸先需要确保队列含有第k个元素
if(hh + k - 1 <= size) printf("%d",q[hh + k - 1]);
else printf("%d", q[hh + k - 1 - size];
三、链表
- 链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的
- 线性表的链式存储表示的特点是用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的)
⼀.单链表
1.单链表⼀般有两种插⼊元素的⽅式,普通插法和头插法和尾插法。
//普通插法
void insert(Node *p, Node *now)
{
now -> next = p -> next;
p -> next = now;
}
//头插法
void insert(Node *now)
{
now -> next = head;
head = now;
}
2.单链表的删除⼀般也有两种⽅式,普通删除和删头法(删除第⼀个元素)
//普通删除,删除now节点
void delete(Node *p Node *now)
{
p -> next = now -> next;
}
//删头法
void delete()
{
head = head -> next;
}
⼆.双链表
1.双链表的插⼊有头尾插⼊和普通插⼊
//普通插法(在任意两个存在的节点中间插⼊)
void insert(Node *p, Node *now)
{
Node *q = p->next;
p->next = now; now->pre=p;
now->next = q;q->pre = now;
}
//头插法
void insert(Node *now)
{
now->next = head;
head->pre = now;
head = now;//更新链头
}
//尾插法
void insert(Node *now)
{
t->next = now;
now->pre=t;
t = now;
}
2.双链表的删除有普通删除和头尾删除。
//普通删除(删除其中的⾮头尾节点)
void delete(Node *now)
{
Node *p = now ->pre, *q = now->next;
p->next = q; q->pre=p;
}
//如果只有⼀个节点 删除只会,head和tail都会变成NULL
//删除头节点 now = head
void delet(Node *now)
{
head = now->next;
head->pre = NULL;
}
//删除尾节点 now = tail
void delet(Node *now)
{
tail = now->pre;
tail->next = NULL;
}
三.循环链表(没有头和尾的概念)
题目描述:洛谷P1996 约瑟夫问题
n 个人围成一圈,从第一个人开始报数,数到 mm 的人出列,再由下一个人重新从 1 开始报数,数到 m 的人再出圈,依次类推,直到所有的人都出圈,请输出依次出圈人的编号。
// 数组模拟链表
#include<iostream>
using namespace std;
int next[1000005];
int main(){
int n,m;
cin>>n>>m;
for(int i=0;i<n;i++)
next[i]=i+1;
next[n]=1;
int p=0;
for(int i=1;i<=n;i++){//开始模拟出圈过程
for(int j=1;j<m;j++)
p=next[p];
cout<<p[next]<<" ";//输出出圈人的位置
next[p]=next[next[p]];//删掉出圈人
}
return 0;
}
四、二叉树
完全⼆叉树以及⼀般⼆叉树的存储与建⽴
⼀.完全⼆叉树的存储:
1.数组模拟
以1为根节点建⽴⼆叉树。对于编号为i的节点,左⼉⼦为i * 2,右⼉⼦为i * 2 + 1,其⽗节点为i/2;
⼆.完全⼆叉树的建⽴:
void Build(int t)
{
Update(t);//跟新t节点的数据
//如果⼦节点存在
Build(t + t);
Build(t + t + 1);
}
三.⼀般⼆叉树的存储
1.数组模拟
如果⽤完全⼆叉树的数组模拟⽅式会浪费很多空间,所以可以⽤数组下标模拟节点编号,
⽤多个数组来记录节点信息。⽤fa[],l[],r[]。分别维护⽗节点和左⼦和右⼦的信息。
2.结构体存储
struct TreeNode{
int value;
int l, r, fa;//下标编号
};
3.指针
struct TreeNode{
int value;
TreeNode *l, *r, *fa;
};
TreeNode *root;//指向根节点
四.⼀般⼆叉树的建⽴
//树的递归定义的,所以建⽴可以⽤递归的⽅式。
void Build(int t)
{
Update(t);//跟新t节点的数据
//如果⼦节点存在
Build(t + t);
Build(t + t + 1);
}
题目描述:洛谷P1030 [NOIP2001 普及组] 求先序排列
给出一棵二叉树的中序与后序排列。求出它的先序排列。(约定树结点用不同的大写字母表示,且二叉树的节点个数 ≤ 8)
#include<iostream>
#include<string>
using namespace std;
void tree(string z,string h){
if (z.length()>0){
cout << h[h.length()-1];
int root=z.find(h[h.length()-1]);
tree(z.substr(0,root),h.substr(0,root));
tree(z.substr(root+1,h.length()-root),h.substr(root,h.length()-root-1));
}
}
int main(){
string a,b;
cin >> a>>b;
tree(a,b);
return 0;
}
题目描述:洛谷P4913 【深基16.例3】二叉树深度
有一个n(n ≤
1
0
6
10^{6}
106) 个结点的二叉树。给出每个结点的两个子结点编号(均不超过 n),建立一棵二叉树(根节点的编号为 1),如果是叶子结点,则输入 0 0。
建好这棵二叉树之后,请求出它的深度。二叉树的深度是指从根节点到叶子结点时,最多经过了几层。
#include <iostream>
#define _for(i, a, b) for (int i=(a); i<=(b); i++)
using namespace std;
const int MAXN = 1e6 + 10;
struct node {
int left, right;
};
node tree[MAXN];
int n, ans;
void dfs(int id, int deep) {
if (id == 0) return ;
ans = max(ans, deep);
dfs(tree[id].left, deep+1);
dfs(tree[id].right, deep+1);
}
int main() {
cin >> n;
_for (i, 1, n) cin >> tree[i].left >> tree[i].right;
dfs(1, 1);
cout << ans << endl;
return 0;
}