最短路径算法 假设图G(V,E), 有n个顶点,e条边,w(u,v)为两点间边的权值,这里分别介绍两种单源点和两种多源点最短路径算法
Dijkstra是典型的最短路径算法,不允许有负权边存在。算法采用的是贪婪算法,普通方法实现的话,时间复杂度为O(n^2),但可以用最小优先队列或者堆实现,可以将时间复杂度降为O(e + nlog(n)) , 这样的算法速度比bellman算法要快。
首先假设几个变量并初始化:
算法步骤:
该算法可以有效快速的求解单源点最短路径问题适用范围:
首先假设几个变量:
关于松弛计算概念:
对某一条边e(u,v),如果 d(v) > d(u) + w(u,v),则 d(v) = d(u) + w(u,v)
算法步骤:
由上述算法可以发现,这个算法的时间复杂度是O(ne), 而且源点到各点的距离要等到最后循环完成之后才能确定。
计算有向图中任意两点之间的距离,可以认为是对单源最短路径的推广
该算法理解起来比较简单, 就是通过检查每条边来确定这条边是不是更短路径的一部分,适用范围:
算法伪码步骤:
初始化:
dist[i][j] = 0, if i=j;
= w[i][j], if i!=j and e(i,j) in E;
= infinite , if i!=j and e(i,j) not in E;
计算最短路径:
for k=1 to n
for i=1 to n
for j=1 to n
if( dist[i][k] + dist[k][j] ) < dist[i][j]
dist[i][j] = dist[i][k] + dist[k][j]
以上可知,该算法也是贪婪算法,时间复杂度为 O(n^3),所以效率比较低
适用范围:
该算法想法比较奇妙,考虑利用bellmanford算法和dijkstra算法,因此需要先加入了一个点到原图。还采用了reweighting的思想,由于Dijkstra算法不允许负权边存在,需要reweighting所有权边,如果G有负权边, reweighting之后所有的边都是非负权边
算法步骤:
这个算法看起来比较复杂,但是其实效率比较高,步骤1的时间复杂度是O(n),步骤2的是O(ne),步骤3的是O(e),步骤4的则是O(enlogn),步骤5的是O(n^2),则最终这个算法的时间复杂度是O(enlogn),比floyd的效率要高。
冒泡排序,依序比较相邻的两个,顺序不对则交换
时间复杂度 O(n^2)
void bsort(int a[], n){
for(int i=0; i<n; i++){
for(int j=0; j<n-i-1; j++){
if(a[j]>a[j+1]){
temp = a[j+1];
a[j+1] = a[j];
a[j] = temp;
}
}
}
}
选择排序,从第一个元素开始往后遍历,每轮从后面选择最小的元素排到最前面。
时间复杂度 O(n^2),排序速度先慢后快,因为要查找的元素的元素逐个减少。大量查找,少量移动。
void selectsort(int a[], n ){
for(int i=0; i<n; i++){
min = a[i];
for(int j = i+1; j<n; j++){
if(a[j]<min){
int temp;
temp = min;
min = a[j];
a[j] = temp;
}
}
a[i] = min;
}
}
将序列分为已排序和未排序两个部分,对未排序部分依序遍历插入前面已排序部分。
先快后慢。后几轮插入时要比较的更多。
依序对后面的n-1个元素遍历,
for i in 1 - n:
将第i个元素插入前面i-1个已排序元素中
void insertionsort(int a[], int n ){
for(int i=1; i<n; i++){
int temp = a[i];
for(int j=i-1;j>=0;j--){
if(a[j]>temp)
a[j+1] = a[j];
else{
a[j+1] = temp;
break;
}
}
}
}
归并排序要调用递归的方法,步骤:
时间复杂度 O(nlogn),空间上需要额外的 n 个空间,不利于基本有序的序列。
void merge(int a[], int low, int mid, int high, int temp[]){
int i =low;
int j = mid+1;
int k =0;
while(i <=mid && j<=high){
if(a[i]<a[j]){
temp[k++]=a[i];
i++;
}
else{
temp[k++]=a[j];
j++;
}
}
while(i<=mid){
temp[k++] = a[i];
i++;
}
while(j<=high) {
temp[k++] = a[j];
j++;
}
for(int i=0; i<k; i++){
a[i+low] = temp[i];
}
}
void mergesort(int a[], int low, int high, int temp[] ){
if(low<high){
int mid = (high+low)/2;
mergesort(a, low, mid,temp);
mergesort(a, mid+1, high,temp);
merge(a, low, mid, high,temp);
}
}
也是要用到递归
时间复杂度 O(nlogn),相对归并排序不需要额外的空间,p 的选择对排序影响比较大,序列基本有序时会相对快。
int partition(int a[], int low, int high){
int midvalue = a[low];
int i = low;
int j = high;
while(i<j){
while(a[j]>midvalue && i<j)
j--;
if(i<j)
a[i++] = a[j];
while(a[i]<midvalue && i<j)
i++;
if(i<j)
a[j--] = a[i];
}
a[i]=midvalue;
return i;
}
void quicksort(int a[], int low, int high){
if(low<high){
int pivot = partition(a, low, high);
quicksort(a, low, pivot-1);
quicksort(a, pivot+1, high);
}
}
大根堆: 父节点比两个子节点都大,小根堆反之。
//调整以第i个节点为父节点的树,使之成为大根堆
int adjust_heap(vector<int> &v, int length, int i){
int left = 2 * i;
int right = 2 * i + 1;
int largest = i;
int temp;
while(left < length || right < length){
if (left < length && v[largest] < v[left]){
largest = left;
}
if (right < length && v[largest] < v[right]){
largest = right;
}
if (i != largest){
temp = v[largest];
v[largest] = v[i];
v[i] = temp;
i = largest;
left = 2 * i;
right = 2 * i + 1;
}
else{
break;
}
}
}
//建立堆,使首节点最大。从最后一个非叶子节点开始,逐个向前,调整为大根堆
int build_heap(vector<int> &v, int length){
int i;
int begin = length/2 - 1; //get the last parent node
for (i = begin; i>=0; i--){
adjust_heap(v,length,i);
}
}
int heap_sort(vector<int> &v){
int length = v.size();
int temp;
build_heap(v,length);
for(int i=n-1;i>=0;i--)
//交换v[0]和v[i]
temp = v[i];
v[i] = v[0];
v[0] = temp;
i--;
//调正前 i-1 个元素是大根堆来获取前i-1的最大值
adjust_heap(v,i,0);
}
}
其实就是分段的插入排序