我们日常中接触的数据大多都是经过排序的. 如按学号顺序在班级表里查询每个学生信息, 按字母顺序在字典中查询每个字的定义. 同理, 工作中也经常会用到排序, 排序算法也是对思维的一个锻炼!
结合自己所学, 我将总结冒泡排序算法, 插入排序算法, 选择排序算法, 三种最基础而又闪烁着程序员智慧之光的算法. 这些算法的每一种都很容易理解和实现. 可能对于大多情况而言, 这些算法不是最好最全面的算法, 但是对于少量数据而言, 用这些算法绝对划算!
一 冒泡排序:
原理(从小到大排序. 可用笔列举几个数, 按下述步骤规划排序一下, 有助于思考)
1. 比较相邻的元素. 如果第一个比第二个大, 就交换他们两个.
2. 对每一对相邻元素做同样的工作, 从开始第一对到结尾的最后一对. 走完这一步, 最后的元素将是最大的.
3. 除最后元素外, 针对剩下元素重复以上步骤.
4. 持续每次对越来越少的元素重复上面的的步骤, 直到没有任何一对数字需要比较.
上代码
- public void bubbleSort(int []arr)
- {
- bool didSwap ;// 元素是否进行过交换
- int temps;
- for (int i = 0; i <arr.Length-1; i++)// 对越来越少的元素进行 1.2.3 步骤
- {
- didSwap = false;
- for (int j = 0; j < arr.Length - 1 - i; j++)// 循环比较任意相邻的两个元素
- {
- if (arr[j]> arr[j + 1])// 如果前一个元素大于后一个元素, 交换他们
- {
- temps = arr[j];
- arr[j] = arr[j + 1];
- arr[j + 1] = temps;
- didSwap = true;
- }
- }
- if (didSwap == false)
- {
- return;
- }
- }
- }
代码分析:
上面的方法体分为两层循环, 内层循环与原理步骤 1.2.3 同理; 外层循环中 i 的值越来越大, 所以内层循环中要比较的元素越来越少, 与步骤 4. 同理.
时间复杂度:
若数组的初始状态是正序的(如数组{1.2.3.4.5....n}), 一趟扫描既可完成排序. 在第一次内循环完成后, didSwap 值为 false, 程序会跳出外循环, 整个方法进行了 n-1 次比较, 0 次交换. 所以最优时间复杂度为 O(n).
若数组的初始状态是反序的, 需要进行 n-1 趟排序. 每趟排序需要进行 n-i 次关键字的比较(1<=i<=n-1), 且每次比较满足条件后都需要进行一次元素交换(交换一次需要三次赋值), 整个方法进行了 n(n-1)/2 次比较, 3n(n-1)/2 次赋值操作, 所以最坏时间复杂度为 O(n^2).
平均时间复杂度为 O(n^2)!
算法稳定性:
冒泡排序就是把小的元素往前调或者把大的元素往后调. 比较是相邻的两个元素比较, 交换也发生在这两个元素之间. 如果两个元素相等, 没有必要交换; 如果两个相等的元素没有相邻, 那么即使通过前面的两两交换把两个相邻起来, 这时候也不必交换, 所以相同元素的前后顺序并没有改变, 所以冒泡排序是一种稳定排序算法.
二, 选择排序
原理:
这种排序是从数组的第一个元素 (位置为 0) 开始, 把第一个元素与数组中其他元素进行比较. 在比较完毕后, 选出最小的元素放置在第 0 个位置上, 接着再从第 1 个位置开始再次进行排序操作. 这种操作会一直到除最后一个元素外的每一个元素都作为新循环的起始点操作后才会终止.
上代码:
- public void selectionSort(int[] arr)
- {
- int temps;
- int pos = 0;
- bool is
- for (int i = 0; i <arr.Length - 1; i++)
- {
- pos = i;
- for (int j = i + 1; j < arr.Length; j++)
- {
- if (arr[j] < arr[pos])
- {
- pos = j;// 获得最小数的下标
- }
- }
- if(pos != i)
- // 第 i 个数与最小数 arr[pos]交换
- temps = arr[i];
- arr[i] = arr[pos];
- arr[pos] = temps;
- }
- }
代码分析:
上述代码使用了两层循环. 外层循环从数组的第一个元素移动到数组最后一个元素之前的元素, 而内层循环则从数组第二个元素移动到数组的最后一个元素, 并且查找比当前外层循环所指元素更小的数值. 在内循环完成一遍后, 便为将得到的最小值赋值到数组中合适的位置.
时间复杂度:
比较次数与数组的初始状态无关, 为 n(n-1)/2 次.
交换次数, 数组为正序时 (pos 总是等于 i) 交换 0 次, 反序交换 n/2 次, 最坏情况交换 n-1 次. 交换次数比冒泡排序少多了, 交换所需 cpu 时间比比较所需 cpu 时间多. 冒泡排序与选择排序的时间复杂度应根据情况而判断
稳定性:
选择排序是给每个位置选择当前元素最小的, 比如给第一个位置选择最小的, 在剩余元素里面给第二个元素选择第二小的, 依次类推, 直到第 n-1 个元素, 第 n 个元素不用选择了, 因为只剩下它一个最大的元素了. 那么, 在一趟选择, 如果一个元素比当前元素小, 而该小的元素又出现在一个和当前元素相等的元素后面, 那么交换后稳定性就被破坏了. 比较拗口, 举个例子, 序列 5 8 5 2 9, 我们知道第一遍选择第 1 个元素 5 会和 2 交换, 那么原序列中两个 5 的相对前后顺序就被破坏了, 所以选择排序是一个不稳定的排序算法.
三 插入排序
原理:
插入排序的基本操作就是将一个元素插入到已经排好序的有序数组中, 从来得到一个新的, 长度加一的数组. 类似于我们平时玩扑克往手中插牌的操作: 摸牌的时候, 牌面朝下放在桌子上, 左手为空. 每一次摸牌后, 将牌大小与手中的牌进行比较, 然后插入适当位置, 这样手中的牌总是排好序的.
上代码
- public void insertSort(int[] arr)
- {
- int temps; // 定义一个变量, 存放要插入的元素
- for (int i = 1; i < arr.Length; i++)
- {
- temps = arr[i];
- int j = i - 1;// 与要插入元素做比较的光标
- while (j>= 0 && arr[j]> temps)
- {
- // 做比较的元素大于要插入的元素, 做比较的元素后移
- arr[j + 1] = arr[j];
- j--;
- }
- // 将要插入的元素插入到适当位置
- if (i != j + 1)
- {
- arr[j + 1] = temps;
- }
- }
- }
代码分析:
上述代码有两层循环. 假设数组中的第一个元素为已排序的数组, 后面 (n-1) 个元素为未排序的数组. 外层循环会遍历未排序数组元素. 内层循环则会把外层循环选择的元素与已排序数组元素从右向左依次比较. 如果外层循环选择的元素小于内层循环选择的元素, 那么内层循环选择的元素向右移动, 留下该处位置, 外层循环的元素再跟上一个元素比较, 直到退出内循环. 然后再外层循环中将外层循环选择的元素插入适当位置
时间复杂度
最好情况, 数组原本是正序数组, 比较次数 n-1, 移动次数 0
最差情况, 数组原本是倒序数组, 比较次数 2+3+.....+n-= (n+2)(n-1)/2, 移动次数 1+2+3+....n-1 = n(n-1)/2.
稳定性:
不会改变同等大小元素的位置, 是稳定的排序算法.
来源: https://www.cnblogs.com/txqx/p/9293107.html