都工作2年了还去看排序算法,是不是有点low?是的,不过咱不会,就得看,还是有些东西值得一观的,我看的是《算法》这鸟书。
排序算法大概讲了,冒泡算法,选择算法,插入算法,希尔算法,归并算法,快排算法,三向切分快排算法,接着讲了优先队列,堆排序,索引优先队列,然后扯皮了几种排序算法的归约(归约是指为解决某个问题而发明的算法正好可以用来解决另一种问题),我看的时候吧,老觉得归并排序这货有点挫,但书中居然证明了它是最坏情况下时间复杂度最低的。。。并且因为其稳定性,在一些应用上选择它而不是快排。下面我手动再捋一下各个算法吧
void Bubble(int[] a) { bool sorted = false; for (int i = a.Length - 1; i > 0; --i) { sorted = true; for (int j = 0; j < i; ++j) { if (a[j] > a[j+1]) { swap(a, j, j+1); sorted = false; } } if (sorted) break; } void Select(int[] a) { for (int i = 0; i < a.Length; ++i) { int min = a[i]; for (int j = i+1; j < a.Length; ++j) { if (a[j] < min) min = j; } swap(a, i, min); } } void Insert(int[] a) { for (int i = 1; i < a.Length; ++i) { for (int j = i; j > 0 && a[j] < a[j-1]; --j) swap(a, j, j-1); /*做法2,稍好一点点 int value = a[i]; for (int j = i-1; j >= 0 && a[j] <value; --j) a[j+1] = a[j]; a[j+1] = value; */ } } // 希尔排序是对插入算法的优化,因为后者可能需要“长途”后移数据,希尔就是解决这个问题的:每次移动数据的距离都不会超过当前步进。最初的步进大小并不会影响很大,只要保证最后的步进是1就好 void ShellSort(int[] a) { int step = 1; while (step < a.Length/3) step = step *3;//如果使用2就step = n/2即可保证最后step = 1 while(step >= 1) { for (int i = step; i < a.Length; ++i) { for (int j = i; j >= step && a[j] < a[j-step]; j = j - step) swap(a, j, j-step); /* int value = a[i]; for (int j = i - step; j >= 0 && a[j] > value; j = j - step) a[j+step] = a[j]; a[j+step] = value; */ step = step / 3; } } } //快排 void QuickSort(int[] a, int low, int high) { if (low >= high) return; int mid = partition(a, 0, a.Length); QuickSort(a, low, mid - 1); QuickSort(a, mid+1, high); } int partition(int[] a, int low, int high) { if (low >= high) return low; int value = a[low]; int left = low; int right = high + 1; while(true) { while(a[++left] < value) if (left == high) break; while(a[--right] > value); if (left >= right) break; swap(a, left, right); } if (right > high) right = high; swap(a, low, right); return right; } // 三向切分快排 void Quick3Sort(int[] a, int low, int high) { if (low >= high) return; int lt = low, i = low+1,gt = high; int value = a[low]; while(i <= gt) { if (a[i] == value) i++; else if (a[i] < value) swap(a, lt++, i++); else swap(a, i, gt--); } Quick3Sort(a, low, lt-1); Quick3Sort(a, gt+1, high); } // 归并排序 void MergeSort(int[] a, int low, int high) { if (low >= high) return; int mid = low + (high - low) / 2; MergeSort(a, low, mid); MergeSort(a, mid+1, high); Merge(a, low, mid, high); } int[] tmpForMerge = null; void Merge(int[] a, int low, int mid, int high) { if (tmpForMerge == null) tmpForMerge = new int[a.Length]; for (int i = low; i <= high; ++i) tmpForMerge[i] = a[i]; int left = low, right = mid; for (int i = low; i <= high; ++i) { if (right > high) a[i] = tmpForMerge[left++]; else if (left == mid) a[i] = tmpForMerge[right++]; else if (a[left] < a[right]) a[i] = tmpForMerge[left++]; else a[i] = tmpForMerge[right++]; } }
我居然在插入代码那里手打完了=。=
优先队列是基于最大/小堆实现的,内部接口就swim(int k)和sink(int k)2个,主要外部接口有插入元素(放最后然后swim(新N)),删除最大/小元素(把首尾交换,sink(1)),优先队列的作用很大,比如取很多数据的top10只需要10个数据空间,图的许多算法也用到了优先队列等。
堆排序就是初始化完最大堆后,每次都交换首尾,接着N--,然后sink(1),done。
优先队列不能改变或读取里面的元素,这个用索引优先队列解决:
索引优先队列有3个数组(也叫平行数组):pq,qp,key
1.pq:是堆存数据的数组,也是堆操作的数据,下标是在堆中的位置(1-N),值是元素的索引(一般是插入顺序)
2.qp:是索引数组,由索引找到该索引在堆中的位置,即pq的反向索引数组,下标是元素索引,值是堆中的位置
3.key:是数据数组,下标是索引,值是元素。
索引获得元素:key;改变元素后,由元素的索引,通过qp找到索引在堆中的位置,sink/swim更新堆(有时候需要同时sink和swim)
可以改变堆中元素,其实作用还是不错的,书中的多个输入流应用,想想也是可以直接用优先队列解决的(反正只要获得最大元素的输入流索引即可,那搞个结构存string和对应输入流索引就解决了)
最后谈了几个归约问题:
1.快速排序是最快的通用排序算法,所以我们一般用快排,不过可以在快排中加入插入排序(lua的sort就是这样的)
2.找出重复元素:排好序后就出来了;
3.Kendall tau距离:见下面代码
public class KendallTau { static long counter = 0; static long distance(int[] a, int[] b) { int N = a.Length; int[] aIndex = new int[N]; int[] bIndex = new int[N]; for (int i = 0; i < N; ++i) { aIndex[a[i]] = i; } for (int i = 0; i < N; ++i) { bIndex[i] = aIndex[b[i]]; //bIndex是b中第i个数在a中的位置,其自身的逆序数就是a和b的逆序数,其中的道理可以类比排列c和标准排列之间的距离就是c本身的逆序数,把a抽象成“标准排列”的话,bIndex就是b相对于这个“标准排列”的“抽象排列” } //用稳定的插入排序/归并排序获得bIndex交换的次数来求得其逆序数即a和b之间的逆序数,也即KendallTau距离 } }
4.中位数和第k小/大数的获得:用快排的切分原理:不断缩小切分范围,然后得到该数:
int select(int[] a, int k) { int low = 0, high = a.length - 1; while(high > low) { int j = partition(a, low, high); if (j == k) return a[j]; else if (j < k) low = j + 1; else high = j - 1; } return a[k]; }
排序的总结就到这里了,书还是不错的。