堆排序,Dijikstra算法和C#

来源:互联网 发布:常用的非线性优化算法 编辑:程序博客网 时间:2024/06/10 08:10

 昨天搞了个通宵,总算把一堆与排序和图的算法用代码给实现了,其中最难的是堆排序和 Dijkstra 最短路径算法。今天早上扬眉吐气,以前望而生畏的东西,现在终于可以信手拈来了。
 
数组排序,以前我用快速排序,不过性能不太稳定,而且一不小心堆栈溢出。堆排序虽然比快速排序平均性能差点,但极端情况下要好得多,性能稳定。
 
经过测试,我的堆排序算法指令耗费为 75.66 N*log2 N,比快速排序平均性能 42.36 N*Log2N 要稍慢些,不过比冒泡法 10.32*N^2 要快许多了。也就是说,只要超过 17 个元素,则快速排序已经好于起泡排序;只要超过 39 个元素,则堆排序要好于起泡排序。
 
至于 Dijkstra 算法,以前上课也讲过,不过好象当时很难听懂。其实没什么难的,估计是那个老师不善于语言表达的缘故。实现起来其实也就一会的工夫。
 
先晾一下代码:
堆排序:

  1.     public class HeapSortHeap {
  2.         public int count;
  3.         public int[] e;
  4.         public HeapSortHeap(int count){
  5.             this.count = count;
  6.             e = new int[count+1];
  7.         }
  8.         public void HeapMake(int count){     //自上向下生成一个堆
  9.             for (int i = count / 2; i > 0; i--) HeapAdjust(i, count);
  10.         }
  11.       //堆排序
  12.         private void HeapAdjust(int idx, int count){  //自上向下调整一个堆
  13.             for (int s = idx; s <= count / 2; ){
  14.                 int type = 0; int k = 0;
  15.                 if (e[s] < e[2 * s]) type |= 1;
  16.                 if (s < count / 2){
  17.                     if (e[s] < e[2 * s + 1]) type |= 2;
  18.                     if (e[2 * s] < e[2 * s + 1]) type |= 4;
  19.                 }
  20.                 switch (type){
  21.                     case 0: k = -1; break;
  22.                     case 1: k = 2 * s; break;
  23.                     case 2: k = 2 * s; break;
  24.                     case 3: k = 2 * s; break;
  25.                     case 4: k = -1; break;
  26.                     case 5: k = 2 * s + 1; break;
  27.                     case 6: k = 2 * s + 1; break;
  28.                     case 7: k = 2 * s + 1; break;
  29.                 }
  30.                 if (k >= 0){
  31.                     e[0] = e[s]; e[s] = e[k]; e[k] = e[0];
  32.                     s = k;
  33.                 }
  34.                 else return;
  35.             }
  36.         }
  37.         public void HeapSort(){
  38.             HeapMake(count);
  39.             for (int i = count; i>=0; ){
  40.               //1. 把最大的记录移到后面去
  41.                 if (e[1] < e[i]) { i--; continue; }
  42.                 e[0] = e[1]; e[1] = e[i]; e[i] = e[0];
  43.                 i--;
  44.               //2. 对当前的堆进行调整
  45.                 HeapAdjust(1,i);
  46.             }
  47.         }
  48.     }

Dijkstra 最短路径算法:

  1.     public delegate void DiagramTranversHandler(int vindex,int parameter);
  2.     class ArcDiagram {
  3.         public int vcount;  //点号从 1 开始
  4.         public int[,] e;    // -1 表示无路径
  5.       //图的生成
  6.         public ArcDiagram(int vcount){
  7.             this.vcount = vcount;
  8.             e = new int[vcount,vcount];
  9.             for (int i = 0; i < vcount; i++) for (int j = 0; j < vcount; j++) e[i, j] = -1;
  10.         }
  11.         public ArcDiagram Clone(){
  12.             ArcDiagram g = new ArcDiagram(vcount);
  13.             for (int i = 0; i < vcount; i++) for (int j = 0; j < vcount; j++) g.set(i + 1, j + 1, e[i, j]);
  14.             return g;
  15.         }
  16.       //Dijkstra 算法
  17.         public void Dijkstra(int v0, DiagramTranversHandler handler){
  18.             v0--;
  19.             int i,j,k,v;
  20.             int[] D = new int[vcount]; bool[] S = new bool[vcount];
  21.             //1. 设 D S 初始状态
  22.             for (i = 0; i < vcount; i++) { S[i] = false; D[i] = e[v0, i]; }; S[v0] = true;
  23.             while (true){
  24.              //2. 取 vj
  25.                 int min = -1; v = -1;
  26.                 for (j = 0; j < vcount; j++) if (S[j] == false && D[j] >= 0 && (min < 0 || min > D[j])) { v = j; min = D[j]; }
  27.                 if (v >= 0) S[v] = trueelse break;
  28.               //3. 修改 V-S 上的 D
  29.                 for (k = 0; k < vcount; k++){
  30.                     if (S[k] == false && e[v, k] > 0){
  31.                         if (D[k] < 0 && D[v] + e[v, k] >= 0) D[k] = D[v] + e[v, k];
  32.                         else if (D[v] + e[v, k] < D[k]) D[k] = D[v] + e[v, k];
  33.                     }
  34.                 }
  35.               //4. 重复
  36.                 bool continueexec = false;
  37.                 for (k = 0; k < vcount && continueexec == false; k++) if (S[k] == false) continueexec = true;
  38.                 if (continueexec == falsebreak;
  39.             }
  40.             for (v = 0; v < vcount; v++) handler(v + 1, D[v]);
  41.         }
  42.     }

这些代码都是用 C# 写的。虽然刚学 C#,但似乎已经觉得 C++ 有点老土了。写 C++ 我总没欲望写出如此规范的代码来。以前我不用 C# 是担心其执行效率,但经测试,C# 好象并不慢。我是用算圆周率的方法来测试的。

 

讲到算圆周率,不得不说这个公式(LaTex 格式)

/sum_1^/infinity /frac{1}{n^2} = /frac{/pi^2}{6}

 

据说当年伯努利不会算,交给欧拉,欧拉把结果给猜出来了。这个公式收敛得很慢,用它来验证代码效率正好。我用 DOS 汇编、Win32 汇编、C++ 和 C# 都实现了一遍。DOS 汇编的那个牵涉到虚 86 模式的效率问题,所以这里晾一下另外三种语言编出来程序的效率(做了多遍,只取最快的记录,以排除线程切换的影响):
算到 300000000,圆周率 3.14159264498239,
汇编语言: 5.521058 秒
C++:     5.359000 秒
C#:      5.250 秒

 

我本来是想看看 C# 比 C++ 慢多少,结果大吃一惊,C# 密集计算比 C++ 还快!难道是 C# 用了比 C++ 更先进的编译优化?看起来其执行时基本上都是本地代码,而不是 C# 反汇编文件中显示的那些中间代码。有高手说原因是缓存机制,不知道是不是 C# 虚拟机代码有实时本地化功能,每次本地化一堆邻近的代码?

 

可以看看我用汇编写的代码,我感觉已经没有优化的余地了:

  1.     mov     i,1
  2.     fldz
  3.     mov     edx,300000000
  4.   LabelMain1_1:
  5.     cmp     i,edx
  6.     jge     LabelMain1_2
  7.     fild    i
  8.     fimul   i
  9.     fld1
  10.     fdivr
  11.     fadd
  12.     inc     i
  13.     jmp     LabelMain1_1
  14.   LabelMain1_2:
  15.     mov     i,6
  16.     fimul   i
  17.     fsqrt
  18.     fstp    pi

在数据面前,我已经感觉,我的手动代码优化基本上没有意义了。记得以前写一个像素拷贝,指令段的优化最理想的情况下能提高 10% 的效率,但数据局域化程度却使程序效率在 100 倍的范围内变化。

 

最后,记得以前数据结构老师告诉,汇编语言不能做数据结构,那时起我才开始学习 C 语言,才第一次真正接触高级语言。然而后来,我不仅用汇编语言编出了动态内存管理,而且什么链表、队列、树、森林、哈希表,我都用汇编语言实现了,而且几乎天天用。虽然那位老师说得不对,不过我还是感谢他逼我学了一门高级语言。现在我已经很久没用汇编了,昨天乍一用连双内存寻址指令不存在都忘了。

原创粉丝点击