整理一下
栈(stack)
特点:后进先出的数据结构
实现:①指针:需要栈顶指针TOP。
②实现:数组(TOP为int型)、链表(TOP为int*型)、stack容器。
用数组实现栈
约定指针: TOP指向最后进入栈中的元素;当栈空时TOP为-1。
👇常见操作:
//数组为st[]
//1 关于空栈——TOP==-1?
void clear(){ //清空(clear)
TOP = -1;
}
bool empty(){ //判空(empty)
if ( TOP==-1 ) return true; //空则返回true
else return false;
}
//2 关于栈的大小(size)
int size(){
return TOP + 1; //栈内元素下标从0开始
}
//3 关于增删
void push(int x){ //进栈(push)
st[++TOP] = x; //TOP指向栈顶元素,增加前应先移动
}
void pop(){ //出栈(pop)
if ( !st.empty() ){ //【需先判空】
TOP--; //移动栈顶指针即可
}
}
//4 关于访问
int top(){ //访问栈顶元素
if ( !st.empty() ){ //【需先判空】
return st[TOP];
}
}
STL容器stack
#include <stack>
using namespace std;
&1 栈的定义
stack<int> st;
&2 栈的增删
//1 入栈(push)
st.push(x); //将x压入栈
//2 出栈(pop)
st.pop(); //弹出栈顶元素
//3 清空(stack没有清空函数)
while( !st.empty() ){ //需依次弹出栈顶元素
st.pop();
} //更常通过重新定义一个栈以变相实现栈的清空
&3 栈的访问
st.top(); //只能访问栈顶元素
&4 栈的大小
//1 判空
st.empty(); //true代表栈空
//2 栈内元素个数
st.size();
队列(queue)
特点:先进先出的数据结构
实现:(1)指针:①队首指针 front(指向队首元素前一个位置),②队尾指针rear(指向队尾元素,即最后一个入队的元素)。
(3)实现:数组(指针为int型)、链表(指针为int*型)、queue容器。
用数组实现队列
👇常见操作:
//数组为q[]
//1 关于空队列——front==rear?
void clear(){ //清空(clear)
front = rear = -1; //【初始为-1】
}
bool empty(){ //判空(empty)【不需为-1】
if ( front==rear ) return true; //空则返回true
else return false;
}
//2 关于队列的大小(size)
int size(){
return rear - front; //依据队列首尾指针的位置关系
}
//3 关于增删
void push(int x){ //入队(push)
q[++rear] = x; //最后入队元素用rear标识
}
void pop(){ //出队(pop)
if ( !q.empty() ){ //【需先判空】
front++; //移动队首指针即可(先入先出)
}
}
//4 关于访问
int front(){ //访问队首元素
if ( !q.empty() ){ //【需先判空】
return q[front+1]; //注意front指向队首前一个元素
}
}
int back(){ //访问队尾元素
if ( !q.empty() ){
return q[rear];
}
}
STL容器queue
#include <queue>
using namespace std;
&1 队列的定义
queue<int> q;
&2 队列的增删
//1 入队(push)
q.push(x); //让x入队
//2 出队(pop)
q.pop(); //队首元素出队
//3 清空(queue没有清空函数)
while( !q.empty() ){ //队首元素依次出队
q.pop();
} //更常通过重新定义一个队列以变相实现队列的清空
&3 队列的访问
q.front(); //访问队首元素
q.back(); //访问队尾元素
&4 队列的大小
//1 判空
q.empty(); //true代表队空
//2 队内元素个数
q.size();
【注意】入队只是制造副本
在入队后,对原元素的修改和对队列中副本的修改互不影响。因此,在需要对队列中元素进行修改而不仅是访问时,队列中存放的最好是元素的编号。
STL容器priority_queue
特点: priority_queue优先队列本质是堆(默认大根堆,优先级最高的在前)。
👉解决一些贪心问题,Dijkstra算法优化。
&1 头文件与定义
#include <queue>;
using namespace std;
priority_queue<int> q;
&2 基本操作
//1 入队push(x)
//2 访问:只能访问队首元素(即堆顶元素,优先级最高的元素)
if ( !q.empty() ) q.top(); //需要判非空才能访问
//3 出队pop()
//4 判空empty()
//5 大小size()
&3 priority_queue内元素优先级的设置
(1)基本数据类型的优先级设置
默认大顶堆,以下两种写法等价👇
priority_queue<int> q;
priority_queue<int, vector<int>, less<int>> q;
第二种写法中,① 第一个参数是队列元素的类型;② 第二个参数是指用来承载底层数据结构堆heap的容器(必须是容器);③ 第三个参数表示对第一个参数的比较类——less表示数字大的优先级越大(大顶堆,递减序列),greater表示数字小的优先级越大(小顶堆,递增序列)见下👇
priority_queue<int, vector<int>, greater<int>> q;
(2)结构体的优先级设置
以以下结构体为例👇
struct fruit{ //水果
string name; //水果名称
int price; //水果价格
};
👉现在,希望按水果的价格高的为优先级高——需要 重载(overload) 小于号“<”(重载是对已有的运算符进行重新定义);且不能重载大于号,只能重载小于号。
struct fruit{
string name;
int price;
friend bool operator < (fruit f1, fruit f2){
return f1.price < f2.price;
}
};
//正常定义即可
priority_queue<fruit> q;
其中,friend为“友元”;“bool operator < (fruit f1, fruit f2)” 对小于号<进行重载。
👉若想要以价格低的水果为优先级高,只需要把return中的小于号改为大于号即可。
struct fruit{
string name;
int price;
friend bool operator < (fruit f1, fruit f2){
return f1.price>f2.price;
}
};
//正常定义即可
priority_queue<fruit> q;
👉能否像sort的cmp一样写在结构体外面呢?
struct fruit{ //水果
string name; //水果名称
int price; //水果价格
}; //原结构体不变
struct cmp{ //把重载单独放在一个结构体中来写
bool operator () (fruit f1, fruit f2){
return f1.price>f2.price;
}
};
//定义优先队列:第三个参数换成用于重载的结构体名
priority_queue<fruit, vector<fruit>, cmp> q;
👉基本数据类型或其他容器(如set)也可以这样定义优先级。
链表
链表结点组成
①数据域,②指针域(指向下一个结点的地址)。
struct node{
typename data; //数据域
node* next; //指针域
};
动态分配内存空间
👇头文件 (new和delete运算符不需要cstdlib头文件)
#include <stdlib.h>
//cpp下,是这样的👇
#include <cstdlib>
using namespace std;
malloc函数(C语言)
必须与free函数成对出现,见下free函数
typename* p = (typename*)malloc(sizeof(typename));
//申请大片空间👇
typename* p = (typename*)malloc(sizeof(typename) * mount);
注:①malloc的参数为 需要申请的内存空间大小。
②malloc的返回值为 指向这块空间的指针(未确定类型 void*)或 NULL(申请失败)。
③强制类型转换:在malloc前加上 (typename*),强制转换为制定类型指针。
new运算符(C++语言)
必须与delete运算符成对出现,见下delete运算符
typename* p = new typename;
//申请大片空间👇
typename* p= new typename[mount];
注: new 返回一个对应类型的指针;申请失败时会启动C++异常机制。
内存泄漏
指使用malloc与new开票出来的内存空间在使用过后没有事放,导致其在程序结束之前始终占据该内存空间——在用完malloc与new开辟出来的空间后必须将其释放。
free函数(对应malloc)
free(p);
注:① free的参数为 指向需释放的内存空间的指针。
②free的效果:释放指针变量p所指向的内存空间;以及将 p 指向空地址 NULL( p本身没有消失)。
free运算符(对应new)
delete(p); //参数也为指向待释放空间的指针
注:使用方法和实现效果均与 free 相同。
链表的基本操作
以 带头结点的链表 为例;注意需要哪些指针变量。
&1 链表的创建
STEP1 首先创建 头结点 head——记录链表,函数返回值。
STEP2 需要 pre指针 存储新建结点的前驱结点——将新建结点连入链表中。
STEP3 循环 创建 新结点p,赋予数据,指向NULL(尾插法),连入已建链表中,移动pre
#include <iostream>
#include <cstdlib>
#include <vector>
using namespace std;
struct node{ //链表结点
int data;
node* next;
};
node* create(vector<int> a); //创建链表
int main()
{
vector<int> a = {5, 3, 6, 1, 2};
node* L = create(a); //新建链表,返回的头指针 head 赋给 L
L = L->next; //从第一个结点开始有数据域
while ( L!=NULL ){
cout << L->data;
if ( L->next!=NULL )
cout << " ";
L = L->next;
}
return 0;
}
//创建链表
node* create(vector<int> a){ //这里传值即可
node *p, *pre, *head; //当前结点p,当前的前驱结点pre,头结点head
//创建头结点
head = new node;
head->next = NULL;
pre = head; //记录pre为head
//创建链表
for ( int i=0; i<a.size(); i++ ){
//新建结点
p = new node;
p->data = a[i];
p->next = NULL;
//将新结点连入链表中
pre->next = p;
//记录下一个新结点的前驱结点
pre = p;
}
return head; //返回链表的头结点指针
}
&2 链表元素的查找
STEP 用 结点p 来 循环扫描 链表。
//在以head为头结点的链表上计数元素x的个数
int search(node* head, int x){
int cnt = 0; //计数器
node* p = head->next; //指针p用于扫描,从第一个结点开始
while( p!=NULL ){
if ( p->data==x )
cnt++;
p = p->next; //扫描下一个结点
}
return cnt;
}
&3 链表元素的插入
STEP1 首先通过 循环 找到 前驱结点pre 的位置——方便插入新结点(将新结点接入链表)。
STEP2 创建 新结点p,赋予数据,指向后继结点(或NULL),再让前驱结点pre指向新结点p,即插入链表。
//将x插入以head为头结点的链表的第pos个位置上
void insert(node* head, int pos, int x){ //插入位置pos编号从1开始
//找到插入位置的前驱结点
node* pre = head;
for ( int i=0; i<pos-1; i++ ){
pre = pre->next;
}
//新建结点,存储x,插入到链表指定位置
node* p = new node;
p->data = x;
p->next = pre->next; //先让新结点指向后继结点
pre->next = p; //再让新结点的前驱结点指向它
}
&4 链表元素的删除
STEP1 需要 指针p 对链表进行扫描,并最终释放 p 所指空间。
STEP2 需要 前驱结点pre,方便架空待释放的 p结点,以达到删除结点的效果。
//删除以head为头结点的链表中所有数据域为x的结点
void del(node* head, int x){
node* p= head->next; //用以扫描和释放,从第一个结点开始
node* pre = head; //保存p的前驱结点
while( p!=NULL ){
if ( p->data==x ){
pre->next = p->next; //架空数据域为x的结点
delete(p); //释放p所指向的内存空间(对应new运算符)
p = pre->next; //后移,判断下一个结点
}
else{
pre = p; //先保存下一个结点的前驱节点
p = p->next; //再后移
}
}
}