一、直接插入排序
1. 思想
直接排序法, 可以分为两个部分, 一部分是有序的, 一部分是无序的.
从这个图上, 应该是能看清楚直接插入排序的思想了.
将无序部分的第一个与有序部分进行比较.
从有序部分的后面向前面比较, 然后不断地挪动有序部分的数据的位置
static void InsertSort(List<int> list) {
//从第二个数开始循环, 循环n-1次 for (int i = 1; i < list.Count; i++) {
//将待排序的数拿出来, 以便后面挪位子 int temp = list[i];
//j就是最后确定的那个最大/最小数的下标 int j = i; while (j >= 1 && temp < list[j - 1]) {
//将满足条件的数据向后移动一位, 腾空位, 为插入挪位子 list[j] = list[j - 1]; j--; } list[j] = temp; } }
2. 复杂度
直接插入排序的最好情况下, 时间复杂度为O(n), 最坏情况下, 复杂度为O(n2);
证明见:
3. 直接插入排序vs快速排序
从上面的代码来看, 直接插入排序需要不断地挪数据. 如果碰到连续整数, 那么挪动的数据就多了. 针对这种问题, 是否可以改进一下直接插入排序?
在比较的时候, 我是否可以跳着比较?
二、希尔排序
1. 思想
在比较的时候, 引入缩小增量比较的方式.
第一步. 使增量d=count/2, 将每隔d个数看成是一组无序的数, 然后对这组无序的数进行插入排序
第二步. 使增量d=d/2, 和第一步执行相同的操作, 一直到d=1的时候
代码:
static void ShellSort(List<int> list) { int step = list.Count / 2; while (step >= 1) { for (int i = step; i < list.Count; i++) { var temp = list[i]; int j = i; while (j >= step && temp < list[j - step]) { list[j] = list[j - step]; j -= step; } list[j] = temp; } step = step / 2; } }
希尔排序与直接插入排序, 中间部分的代码基本一直, 不同的只是维度, 直接插入排序的维度是固定的1,
而希尔排序的维度是变化的. 从代码上看, 其实还是蛮简单的, 就拿着直接插入排序改吧改吧就成了.
2. 复杂度
希尔排序的时间复杂度, 和直接插入排序的最好&最坏时间复杂度居然是一样的, 同志们, 能相信么.
三、直接插入排序 vs 希尔排序
既然说希尔排序是直接插入排序的改进版, 那么他们究竟谁更厉害些呢? 会不会越改越差了?
static void Test() { //五次比较 for (int i = 1; i <= 5; i++) { List<int> list = new List<int>(); List<int> listA = new List<int>(); //插入2k个随机数到数组中 for (int j = 0; j < 10000; j++) { Thread.Sleep(1); list.Add(new Random((int)DateTime.Now.Ticks).Next(0, 100000)); } listA.AddRange(list); Console.WriteLine(" 第" + i + "次比较:{0}...", string.Join(",", list.Take(10))); Stopwatch watch = new Stopwatch(); watch.Start(); InsertSort(list); watch.Stop(); Console.WriteLine(" 直接插入排序耗费时间:" + watch.ElapsedMilliseconds); Console.WriteLine("输出前是十个数:" + string.Join(",", list.Take(10).ToList())); watch.Restart(); ShellSort(listA); watch.Stop(); Console.WriteLine(" 希尔排序耗费时间:" + watch.ElapsedMilliseconds); Console.WriteLine("输出前是十个数:" + string.Join(",", listA.Take(10).ToList())); } }
从结果上看, 希尔排序的改进效果还是蛮明显的. 但是希尔排序并不是一个稳定的排序方式. 也就是说, 还是可能出现比快速排序慢的时候.