数据结构之线性结构(《算法笔记》)

栈(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;   //再后移 
		}
	} 
} 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值