1.冒泡排序(o(n2))
- 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
- 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
这个算法的名字由来是因为越大的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。
/// <summary> /// 交换数据 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="data"></param> /// <param name="i"></param> /// <param name="j"></param> public static void Swap<T>(IList<T> data, int i, int j) where T:IComparable{ T temp = data[i]; data[i] = data[j]; data[j] = temp; } /// <summary> /// 冒泡排序(有哨兵) /// </summary> /// <param name="data"></param> public static void BubbleSortWithSentry<T>(IList<T> data) where T : IComparable { bool flag; for (int i = data.Count - 1; i > 0; i--) { flag = true; for (int j = 0; j < i; j++) { if (data[j].CompareTo(data[j + 1]) > 0) { Swap(data, j, j + 1); flag = false; } } if (flag) break; } } /// <summary> /// 冒泡排序 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="data"></param> public static void BubbleSort<T>(IList<T> data) where T : IComparable { for (int i = data.Count - 1; i > 0; i--) { for (int j = 0; j < i; j++) { if (data[j].CompareTo(data[j + 1]) > 0) Swap(data, j, j + 1); } } }
冒泡排序过程分析:把最大的放到最后
///运行测试
Stopwatch watch = new Stopwatch(); for (int k = 0; k < 100; k++) { Random rd = new Random(); List<int> bubbleSortData = new List<int>(); List<int> bubbleSortWithSentryData = new List<int>(); for (int i = 0; i < 100; i++) { int d = rd.Next(1000); bubbleSortData.Add(d); bubbleSortWithSentryData.Add(d); } watch.Restart(); SuanFa.BubbleSort(bubbleSortData); watch.Stop(); long bubbleSortTime = watch.ElapsedTicks; watch.Restart(); SuanFa.BubbleSortWithSentry(bubbleSortWithSentryData); watch.Stop(); long bubbleSortWithSentryTime = watch.ElapsedTicks; Console.WriteLine($"时间差: {bubbleSortWithSentryTime - bubbleSortTime}"); }
有哨兵和没有哨兵的运行结果分析,并不是每次有哨兵的都小于没有哨兵的,相反有哨兵的运行时间与没有哨兵的运行时间差小于0的次数比小于50%。if运行是需要花费时间的,基本上在2个运行周期。
2.选择排序(o(n2))
选择排序算法的原理如下:
每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。
/// <summary> /// 选择排序 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="data"></param> public static void SelectSort<T>(IList<T> data) where T : IComparable { int min; for (int i = 0; i < data.Count - 1; i++) { min = i; for (int j = i + 1; j < data.Count; j++) { if (data[j].CompareTo(data[min]) < 0) min = j; } if (min != i) Swap(data, i, min); } }
选择排序分析:把最小的放到最前面
3.插入排序(o(n2))
插入排序有以下几种:直接插入排序,二分插入排序(又称折半插入排序),链表插入排序,希尔排序(又称缩小增量排序)
直接插入排序算法的原理如下:
把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列。
/// <summary> /// 直接插入排序 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="data"></param> public static void InsertSort<T>(IList<T> data) where T : IComparable { int min; T temp; for (int i = 1; i < data.Count; i++) { temp = data[i]; min = i; for (int j = i - 1; j >= 0; j--) { if (temp.CompareTo(data[j]) >= 0) break; data[j + 1] = data[j]; min = j; } if (min != i) data[min] = temp; } }
插入排序分析:把一个数与一个已经排好的序的最大值比较,如果比最大值大直接插入,否则最大值依次后移,把索引保存,最后插入要插入的数据。
折半插入/二分插入排序的原理如下:
折半插入排序(binary insertion sort)是对插入排序算法的一种改进,由于排序算法过程中,就是不断的依次将元素插入前面已排好序的序列中。由于前半部分为已排好序的数列,这样我们不用按顺序依次寻找插入点,可以采用折半查找的方法来加快寻找插入点的速度。
/// <summary> /// 折半插入/二分插入排序 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="data"></param> public static void BinaryInsertSort<T>(IList<T> data) where T : IComparable { int pre, end, mid; T temp; for (int i = 1; i < data.Count; i++) { temp = data[i]; pre = 0; end = i - 1; while (pre <= end) { mid = (pre + end) / 2; if (temp.CompareTo(data[mid]) >= 0) pre = mid + 1; else end = mid - 1; } for (int j = i; j > pre; j--) data[j] = data[j - 1]; if (pre != i) data[pre] = temp; } }
折半插入排序:折半插入排序,使用使用折半查找的方式寻找插入点的位置, 可以减少比较的次数,但移动的次数不变, 时间复杂度和空间复杂度和直接插入排序一样,在元素较多的情况下能提高查找性能。折半插入的关键点在于找到插入的位置。
4.快速排序(o(nlogn))
快速排序算法的原理如下:
快速排序由C. A. R. Hoare在1962年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
/// <summary> /// 快速排序 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="data"></param> public static void QuickSort<T>(IList<T> data) where T : IComparable { QuickSort(data, 0, data.Count - 1); } public static void QuickSort<T>(IList<T> data, int left, int right) where T : IComparable { if (left < right) { //取中间的元素作为比较基准,小于他的往左边移,大于他的往右边移 T middle = data[(left + right) / 2]; int l = left - 1; int r = right + 1; while (true) { while (data[++l].CompareTo(middle) < 0 && l < right) ; while (data[--r].CompareTo(middle) > 0 && r > 0) ; if (l >= r) break; Swap(data, l, r); } QuickSort(data, left, l - 1); QuickSort(data, r + 1, right); } }
快速排序分析:注意基准数据永远不变,永远是和基准数据进行比较,无论在什么位置,最后的目的就是把基准数据放在中间,小的放左边大的放右边
5.归并排序
归并排序算法的原理如下:
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
/// <summary> /// 归并排序 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="data"></param> public static void MergeSort<T>(IList<T> data) where T : IComparable { MergeSort(data, 0, data.Count - 1); } public static void MergeSort<T>(IList<T> data, int left, int right) where T : IComparable { if (left < right) { int mid = (left + right) / 2; MergeSort(data, left, mid); MergeSort(data, mid + 1, right); Merge(data, left, mid, right); } } private static void Merge<T>(IList<T> data, int left, int mid, int right) where T : IComparable { T[] temp = new T[data.Count]; int l = left; int m = mid + 1; int t = l; while (l <= mid && m <= right) { if (data[l].CompareTo(data[m]) <= 0) temp[t++] = data[l++]; else temp[t++] = data[m++]; } while (l <= mid) temp[t++] = data[l++]; while (m <= right) temp[t++] = data[m++]; for (int i = left; i <= right; i++) data[i] = temp[i]; }