堆优化——dijkstra算法
先说dijkstra算法,一种找最短路的算法,在之前的博客写过,不知道的可以看下http://t.csdn.cn/JR4II
朴素版的dijkstra算法,用的是邻接矩阵存图
#include<stdio.h>
#define size 10001
#define most 0x3f3f3f3f
int dist[size];//起点到其余各顶点的最短路径长度,初始化为无穷大
int before[size];//顶点到其所在的最短路径上的前一个顶点的数组下标信息,初始化为-1,可给出源点到此点的最短路径的具体路径
bool book[size];//用来标记顶点是否已经并入最短路径,初始化为false表示没有被纳入最短路径
int map[size][size];//用来存储各顶点之间的关系,map[x][y]中存储的值表示为x点到y点的距离
int n, m, s;//分别表示点的个数,有向边的个数,出发点(源点)的编号
void init() { //算法初始化
book[s] = true; //标记源点,将源点纳入最短路径
for (int i = 1; i <= n; i++) {
if (i != s) {
book[i] = false;//初始化用来标记的数组
}
before[i] = -1; //初始化前驱数组
for (int li = 1; li <= n; li++) {//初始化存储图数据的数组
if (i != li) {
map[i][li] = most; //无穷值认为两个顶点之间不能到达
} else {
map[i][li] = 0; //一个顶点到自身顶点之间的距离为0
}
}
}
}
void run_dijkstra() {
for (int i = 1; i <= n - 1; i++) {
int min = most, x;
for (int j = 1; j <= n; j++) {//找到离源点最近且没有被纳入最短路径的顶点,设该点为x点
if (!book[j] && dist[j] < min) {
min = dist[j];
x = j;
}
}
book[x] = true;//将离更新点最近的点x点标记
for (int j = 1; j <= n; j++) {//更新与x点相连的点的到源点距离
if (!book[j] && (dist[x] + map[x][j] < dist[j])) {//此点没有被标记且通过x点做中转点可缩短此点到源点的距离
dist[j] = dist[x] + map[x][j];//更新此点到源点的距离
before[j] = x;//更新此点的前驱点为x点
}
}
}
}
int main() {
scanf("%d %d %d", &n, &m, &s);
init();//输入n,m,s之后再初始化
for (int i = 0; i < m; i++) { //输入数据
int a, b, c;
scanf("%d %d %d", &a, &b, &c);
map[a][b] = c;
}
for (int i = 1; i <= n; i++) { //初始化dist数组,这里初始与源点想连的其余各个顶点的路程
dist[i] = map[s][i];
}
run_dijkstra();
for (int i = 1; i <= n; i++) {
printf("%d ", dist[i]);
}
}
时间复杂度太高,数据大就不行了,于是可以用堆,用堆来优化
什么是堆?
如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<=K2i+2 ,则称为小堆(或大堆)。
将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
怎么去优化?优化原理是什么?
朴素版每次在dist数组中找最小值都会耗费大量时间,怎么快速的找到dist数组中的最小值?
将dist数组排序,显然不可能,于是联想到小根堆的特点,通过维护堆,每次可直接找到一个数组中的最小值,这样就可以大大地降低时间复杂度了
邻接表+手写堆代码奉上
#include<stdio.h>
#include<string.h>
#define Size 999999
int dist[Size];//各点到源点的最小距离
bool book[Size];//标记是否并入最小路径
int n, m, s; //分别是顶点个数,边个数,源点
int heap[Size];//一维数组存储堆,堆里面存储的是*dist数组的下标*,请注意,这里并不
//是按照顶点编号的大小来建立最小堆的,而是按照顶点在数组dist中所对应的值dist[heap[i]]的大小来建立这个最小堆的
int postion[Size];//存编号在堆(heap数组)中的位置,如postion[6]=2表示dist数组中下标(编号)为6在heap
//数组中有heap[2]=6,例如:postion[3]=8,对应的heap[8]=3
int size;//堆的大小
int first[Size], next[Size];//邻接表存图
int u[Size], v[Size], w[Size];//边的两个顶点以及该边的权值
void init() {//初始化
memset(book, 0, sizeof(book));
memset(first, -1, sizeof(first));
memset(dist, 0x7f, sizeof(dist));
size = n;
for (int i = 1; i <= size; i++) {//将编号随便放入堆
heap[i] = i;
postion[i] = i;
}
}
void swap(int x, int y) { //交换堆中的两个元素,x,y分别为堆中元素的标号
int t = heap[x];
heap[x] = heap[y];
heap[y] = t; //交换
int tt = postion[heap[x]];
postion[heap[x]] = postion[heap[y]];
postion[heap[y]] = tt; //同步更新pos数组
}
void siftdown(int i) { //下滤(向下调整函数),i为向下调整的结点编号
int t, flag = 0; //flag用来标记是否继续向下调整
while (i * 2 <= size && flag == 0) {
if (dist[heap[i]] > dist[heap[2 * i]]) {//比较i和它左儿子i*2在dist数组中的值,并用t记录较小的结点的编号
t = i * 2;
} else {
t = i;
}
if (i * 2 + 1 <= size) { //如果它有右儿子,再对右儿子进行讨论
if (dist[heap[t]] > dist[heap[i * 2 + 1]]) {//右儿子的值更小,更新较小的结点标号
t = i * 2 + 1;
}
}
if (t != i) { //如果发现最小的结点编号不是自己,说明子节点中有比父节点更小的
swap(t, i); //交换它们
i = t; //更新i为刚才与它交换的儿子的结点的编号
} else {
flag = 1; //否则说明当前的父节点已经比两个子节点都要小了,不需要在进行调整了
}
}
}
void siftup(int i) {
int flag = 0; //flag用来标记是否继续向上调整
if (i != 1) {//如果是堆顶就返回,不需要调整,
while (i != 1 && flag == 0) { //不在堆顶,且当前结点i的值比父节点小的时候继续向上调整
if (dist[heap[i]] < dist[heap[i / 2]]) { //判断是否比父节点小
swap(i, i / 2); //交换它和它父节点的位置
} else {
flag = 1; //不需要再次调整
}
i = i / 2; //这句话很重要,更新编号i为它父节点的编号,以便于下一次继续向上调整
}
}
}
int pop() {//从堆顶取出一个元素
int t = heap[1]; //记录堆顶点的值
postion[t] = 0;
heap[1] = heap[size]; //将堆的最后一个顶点的编号赋值到堆顶
postion[heap[1]] = 1;
size--;//堆的元素减少1
siftdown(1);//将堆顶的元素向下调整
return t;//返回记录的堆顶点的值
}
void run_dijkstra() {
book[s] = true;
dist[s] = 0; //将源点并入最短路径
int k = first[s]; //以s为源点的边
while (k != -1) {//初始化源点到其余各个顶点的初始距离
dist[v[k]] = w[k];
k = next[k];
}
for (int i = size / 2; i >= 1; i--) {//维护形成小根堆
siftdown(i);//下滤调整
}
pop();//先弹出一个堆顶元素,因为此时堆顶是s号元素,将源点弹出
for (int i = 1; i <= n - 1; i++) {
int j = pop(); //j为当前dist数组中最小元素的下标
book[j] = true;
int k = first[j];
while (k != -1) { //邻接表遍历同顶点的边
if (!book[v[k]] && dist[v[k]] > dist[j] + w[k]) { //以j点为中转点更新其他未入最短路径的点到源点的距离
dist[v[k]] = dist[j] + w[k];
siftup(postion[v[k]]);//对该点在堆中进行向上调整以维护堆
}
k = next[k];
}
}
}
int main() {
scanf("%d %d %d", &n, &m, &s);
init();
for (int i = 1; i <=m; i++) { //开始读入边
scanf("%d %d %d", &u[i], &v[i], &w[i]);
}
for (int i = 1; i <= m; i++) { //开始使用邻接表存储边(不存储边的权值)
next[i] = first[u[i]];//first数组初始值为-1
first[u[i]] = i;
}
run_dijkstra();
for (int i = 1; i <= n; i++) {
printf("%d ", dist[i]);
}
}
今天还浅学了一下STL库的优先队列
#include<cstdio>
#include<queue> //不要忘记头文件
using namespace std;
priority_queue<int,vector<int>,greater<int> > q; //定义优先队列,升序
int n,a,b;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a);
if(a==1) //根据题目要求来
{
scanf("%d",&b);
q.push(b); //把b压入优先队列
}
if(a==2)
{
int ans=q.top(); //获取现在优先级最高的元素的值
printf("%d\n",ans); //输出答案
}
if(a==3)
q.pop(); //将优先级最高的元素弹出
}
}
堆——优先队列
struct node {
long long value, x;
bool operator < (const node &nd)const {
return nd.value < value;//小于,是小根堆
}
// 重载运算符
} heap;
priority_queue <node> que; //堆
//根据dis的大小来形成堆,x只是一个编号
void rudui(int val,int x){//入堆
heap.value=val,heap.x=x;
que.push(heap);
}
STL库堆优化+邻接矩阵
//空间复杂度过大,容易memory limit exceeded内存超限
#include<stdio.h>
const int size = 10001;
#define most 0x3f3f3f3f
#include<queue> //不要忘记头文件
using namespace std;
struct node {
long long dis, x;
bool operator < (const node &nd)const {
return nd.dis < dis;//小于,是小根堆
}
// 重载运算符
} heap;
priority_queue <node> q; //堆
int dist[size];//起点到其余各顶点的最短路径长度,初始化为无穷大
bool book[size];//用来标记顶点是否已经并入最短路径,初始化为false表示没有被纳入最短路径
int map[size][size];//用来存储各顶点之间的关系,map[x][y]中存储的值表示为x点到y点的距离
int n, m, s;//分别表示点的个数,有向边的个数,出发点(源点)的编号
void init() { //算法初始化
book[s] = true; //标记源点,将源点纳入最短路径
for (int i = 1; i <= n; i++) {
if (i != s) {
book[i] = false;//初始化用来标记的数组
}
for (int li = 1; li <= n; li++) {//初始化存储图数据的数组
if (i != li) {
map[i][li] = most; //无穷值认为两个顶点之间不能到达
} else {
map[i][li] = 0; //一个顶点到自身顶点之间的距离为0
}
}
}
dist[s] = 0;
}
void run_dijkstra() {
heap.x = s, heap.dis = 0;
q.push(heap);
while(!q.empty()) {
heap=q.top();//取出所有dist数组中最小的值
q.pop();//删除最小值
int min = heap.dis, x=heap.x;
book[x] = true;//将离更新点最近的点x点标记
for (int j = 1; j <= n; j++) {//更新与x点相连的点的到源点距离
if (!book[j] && (dist[x] + map[x][j] < dist[j])) {//此点没有被标记且通过x点做中转点可缩短此点到源点的距离
dist[j] = dist[x] + map[x][j];//更新此点到源点的距离
heap.x=j,heap.dis=dist[j];
q.push(heap);
}
}
}
}
int main() {
scanf("%d %d %d", &n, &m, &s);
init();//输入n,m,s之后再初始化
for (int i = 0; i < m; i++) { //输入数据
int a, b, c;
scanf("%d %d %d", &a, &b, &c);
map[a][b] = c;
}
for (int i = 1; i <= n; i++) { //初始化dist数组,这里初始与源点想连的其余各个顶点的路程
dist[i] = map[s][i];
heap.dis=dist[i];
heap.x=i;
q.push(heap);
}
run_dijkstra();
for (int i = 1; i <= n; i++) {
printf("%d ", dist[i]);
}
}
STL库堆优化+链式前向星
#include<stdio.h>
#include<string.h>
#define most 0x3f3f3f3f
#include<queue>
using namespace std;
const int size = 10001;
struct node {
long long value, x;
bool operator < (const node &nd)const {
return nd.value < value;//小于,是小根堆
}
// 重载运算符
} heap;
priority_queue <node> que; //堆
void rudui(int val, int x) { //入堆
heap.value = val, heap.x = x;
que.push(heap);
}
long long dist[size];//起点到其余各顶点的最短路径长度,初始化为无穷大
//可给出源点到此点的最短路径的具体路径
bool book[size];//用来标记顶点是否已经并入最短路径,初始化为false表示没有被纳入最短路径
int n, m, s;//分别表示点的个数,有向边的个数,出发点(源点)的编号
struct edges { //链式前向星存图
int x;//该边的尾(edge[i].x表示第i条边的终)
int distan;//此边的权值
int next;//同起点的下一条边在edge数组中的储存位置
} edge[size * 10000]; //用来存边
int num;//对边进行编号的
int head[size];//head[i]表示以i为起点的第一条边在edge数组中的位置,即edge数组的下标
void toin(int a, int b, int c) { //存边,a,b,c分别表示两个顶点和该边的权值(距离)
num++;//编号增加
edge[num].x = b;//
edge[num].distan = c;//存入该边的权值
edge[num].next = head[a];//将以a为起点的前一条边的编号放在这要存的这条边的next里面
head[a] = num;//让head[a]等于存入的这条边的编号
}
void init() { //算法初始化
num = 0; //编号初始化为0
memset(book, 0, sizeof(book));
memset(dist, 0x3f3f3f3f, sizeof(dist));
dist[s] = 0;
}
void run_dijkstra() {
for (int i = head[s]; i != 0; i = edge[i].next) { //与源点连接的点初始化dist数组
dist[edge[i].x] = edge[i].distan;
rudui(dist[edge[i].x], edge[i].x);
}
book[s] = 1;
while (!que.empty()) {
heap = que.top(); //取出所有dist数组中最小的值
que.pop();//删除最小值
int min = heap.value, g=heap.x;
book[g] = true;//将离更新点最近的点x点标记
for (int i = head[g]; i != 0; i = edge[i].next) { //链式前向星开始以g为始顶点搜边
if (!book[edge[i].x] && dist[edge[i].x] > dist[g] + edge[i].distan) {
dist[edge[i].x] = dist[g] + edge[i].distan;
rudui(dist[edge[i].x],edge[i].x);
}
}
}
}
int main() {
scanf("%d %d %d", &n, &m, &s);
init();//输入n,m,s之后再初始化
for (int i = 0; i < m; i++) { //输入数据
int a, b, c;
scanf("%d %d %d", &a, &b, &c);
toin(a, b, c);
}
run_dijkstra();
for (int i = 1; i <= n; i++) {
printf("%d ", dist[i]);
}
}