一、基础概念
1、冒泡排序:不断迭代,每次把最大值往右靠
2、选择排序:不断迭代,每次把最小值往左靠
3、插入排序:不断迭代,每次维护一个有序数组,并且将当前元素插入到有序数组合适位置
4、希尔排序:枚举增量,对不同下标增量进行分组,分别执行插入排序,再减小增量重复执行
5、归并排序:每次将数组分成长度接近的两部分,分别执行归并排序后。进行合并,再回溯
6、快速排序:选择一个基点,比基点小的元素到它左边,比基点大的元素到它右边,分别对两堆递归执行快速排序
7、计数排序:一次遍历,将所有数组映射到哈希表,再遍历哈希表进行输出
8、基数排序:从最低的位开始映射到0-9的痛,利用计数排序思想,让最低位先保证有序,在处理次低位,直到最高位处理完毕后,数组有序
二、动图演示
1、冒泡排序:
2、选择排序:
3、插入排序:
4、希尔排序:
5、归并排序:
6、快速排序:
7、计数排序:
8、基数排序:
三、八大排序复杂度
排序前后,相同元素的相对位置不变,则称排序算法是稳定的;否则 排序算法是 不稳定的
四、原理说明
1、归并排序
说明:
分治思想,先拆,按照对半的长度拆,一直拆到都只剩一个元素的子数组,然后,重新组合。
递归(深度优先遍历),按照索引长度处理
步骤:
- 按照长度半,分左右递归拆分
- 聚合,遵从递归的逻辑,先从最底层聚合
-
-
定义临时数组,存放当前截取索引长度的数组数据
-
将截取的数组内容,排序存放到临时数组
-
将临时数组内容拷贝至原数组索引位置
-
步骤拆解:
如数组:8,4,5,7,1,6,3,2
第一步:递归拆解之后,得:
第二步:相邻两组进行合并:
最终得:
第三步,继续相邻合并
定义2个变量i和j,分别代表p1的第一个值和p2的第一个值,i和j比较,4<5,将4放入p中,i向后挪一位
i和j继续比较,8>5,将5放入p,j向后挪一位
继续比较,得到下面2个数组
采用同样的方式比较
代码:
public static void main(String[] args) {
int[] nums = {1,2,2,5,4,3,765,34,65};
sortAsc(nums, 0, nums.length-1);
System.out.println(Arrays.toString(nums));
}
private static void sortAsc(int[] nums, int low, int high){
int middle = (low + high) / 2;
if (low < high){
//处理左边数组
sortAsc(nums, low, middle);
//处理右边数组
sortAsc(nums, middle+1, high);
//归并
merge(nums, low, middle, high);
}
}
private static void merge(int[] nums, int low, int middle, int high) {
int[] temp = new int[high-low+1];
int i = low;
int j = middle+1;
int index = 0;
while (i<=middle && j<=high){
if (nums[i] < nums[j]){
temp[index++] = nums[i++];
} else {
temp[index++] = nums[j++];
}
}
while (i<=middle){
temp[index++] = nums[i++];
}
while (j<=high){
temp[index++] = nums[j++];
}
for (int k = 0; k<temp.length; k++){
nums[k+low] = temp[k];
}
}
2、快速排序
说明:
- 分支思想,找一个基准数,先从后往前找,(以升序为例),找比基准数小的数,将其赋予基准那个位置;再从前往后找,找比基准数大的数,放到刚刚那个位置,对比结束,将基准数给到最后找到的那个数的位置。一句话:找基准,分别找小数和大数,每次能找到2个数,所以叫做快速排序
步骤:
-
- 取数组最左侧数,为基准数,记录下来作为一个临时变量t
- 从数组最右侧开始找,找比基准数小的数,找到了,就给到基准数位置处,那么此时,数组是一个错误的数组的,因为基准数位置被赋予了找到的那个较小的数
- 从数组左侧开始找,找比基准数大的数,找到了,就给到上一步找大数的那个位置,此时,数组中,当前位置的数是不对的,需要将基准数放到这
- 将基准数放到最后一个位置
- 至此,找出了一个比基准数大的数,一个比基准数小的数,并且他们3个的位置顺序已确定
- 依此类推,递归找基准数左右两边的
步骤拆解
如:数组:5,3,7,6,4,1,0,2,9,10,8。
①、base=5, start=0,end=11,从后往前找,第一个比5小的数是 2(i=8),因此,数组变为:
2,3,7,6,4,1,0,5,9,10,8。此时,start=0, end=8;
②、base=5, start=1,end=8,从前往后找,第一个比5大的数是7(i=2),因此,数组变为:
2,3,5,6,4,1,0,7,9,10,8。此时,strat=2, end=8;
③、base=5,satrt=2,end=8,从i=8的位置开始前找,到i=2的位置截止,找到比5小的数,为0(i=6),此时,数组变为:2,3,0,6,4,1,5,7,9,10,8。此时,start=2,end=6;
④、base=5,start=2,end=6,从i=2的位置开始往后找,到i=6截止,找到比5大的数,为6(i=3),此时数组变为:2,3,0,5,4,1,6,7,9,10,8。此时,strat=3,end=6;
⑤、base=5, start=3,end=6,从i=6的位置开始往前找,到i=3截止,找到比5小的数,为1(i=5),此时,数组变为:2,3,0,1,4,5,6,7,9,10,8 。此时,start=3,end=5;
⑥、base=5, start=3, end=5,从i=3的位置往后找,到i=5截止,找到比5大的数,没有,数组不变,此时 i=j=5,循环条件不满足,退出循环,第一次排序结束,得到:i=j=5,数组为:
2,3,0,1,4,5,6,7,9,10,8
⑦、将数组以i=5分为2部分,第一部分:2,3,0,1,4,5 ;第二部分:6,7,9,10,8
⑧、将2个子数组分别执行1-6的操作,即可得到每个子数组的排序,整体数组即完成排序
0,1,2,3,4,5,6,7,8,9
代码:
public static void main(String[] args) {
int[] nums = {2,1,3,5,1,6,5,9};
sortAsc(nums, 0, nums.length-1);
System.out.println(Arrays.toString(nums));
}
private static void sortAsc(int[] nums, int start, int end){
if (start < end) {
//选中基准数
int base = nums[start];
int i = start;
int j = end;
while (i < j) {
//从后往前找,找到比基准数小的数,往前移动
while (i < j && nums[j] >= base) {
j--;
}
//此时的j就是找到的数的目标位置,两者进行对调
nums[i] = nums[j];
//然后从前往后找,找到比基准数大的数,往后移
while (i < j && nums[i] <= base) {
i++;
}
//此时的i就是找到的目标位置,进行对调
nums[j] = nums[i];
}
//把基准数给到i的位置
nums[i] = base;
//左区间递归处理
sortAsc(nums, start, i);
//右区间递归处理
sortAsc(nums, i + 1, end);
}
}
3、选择排序
说明:
- 每次找到剩余数组的最小值,按照索引顺序依次排列
步骤:
- 遍历全部数据,获取最小数,与index=0的数互换位置
- 遍历index>=1的所有数据,获取最小数,与index=1的数互换位置
- 遍历index>=2的所有数据,获取最小是,与index=2的数互换位置
- 依次类推
步骤拆解:
如:10,14,27,33,35,19,42,44
代码:
public static void main(String[] args) {
int[] nums = {2,1,3,3,4,9,54,32,21};
sortAsc(nums);
System.out.println(Arrays.toString(nums));
sortDescForHeap(nums);
System.out.println(Arrays.toString(nums));
}
/**
* 简单选择排序
* */
private static void sortAsc(int[] nums) {
for (int i = 0; i < nums.length; i++) {
int minIndex = i;
for (int j = i+1; j < nums.length; j++) {
if (nums[j] < nums[minIndex]) {
minIndex = j;
}
}
//compare and swap
if ( i != minIndex) {
int temp = nums[i];
nums[i] = nums[minIndex];
nums[minIndex] = temp;
}
}
}
4、堆排序
说明:
- 大顶堆:完全二叉树,根节点大于左右节点,公式:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
- 小顶堆:完全二叉树,根节点小于左右节点,公式:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]
步骤:
- 将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;
- 将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;
- 重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。
步骤拆解
如:给出数组:4, 5, 8, 2, 3, 9, 7, 1
1、构建大顶堆:
2、找到最后一个非叶子节点,即元素 2,比较它的左右节点,是否比他大,如果大就交换位置,1<2,不用交换位置
3、比较下一个,即元素 8,比他的左子节点 9 小,故需要交换位置:
4、比较下一个非叶子节点 5,比他的左右节点大,故不需要调整
5、比较下一个非叶子节点 4,比他的右节点 9 小,估需要交换位置:
6、此时,右节点 4,8,7,根节点比左子节点 8 小,故需要交换位置:
7、至此,大顶堆构建完成
8、排序,将堆顶元素,放到最后面,如此往复,即可得到升序的数列;如果要做降序,则用小顶堆即可(升序,先找最大值;降序,先找最小值——与选择排序的的区别)
代码:
public static void main(String[] args) {
int[] nums = {2,1,3,3,4,9,54,32,21};
sortHeap(nums);
System.out.println(Arrays.toString(nums));
}
private static void sortHeap(int[] nums){
//1、构建大顶堆 //i初始值来自大顶堆定义:
//大顶堆:nums[i]>=nums[2i+1] && nums[i] >= nums[2i+2]
//note:小顶堆定义:nums[i] <= nums[2i+1] && nums[i] <= nums[2i+2]
//要从第一个非叶子节点,则其至少要有一个叶子节点,
//即2i+1 为叶子节点 或 2i+2为叶子节点
// 若2i+2为叶子,则2i+1肯定存在,范围过大,
//因此,只需要满足2i+1为叶子节点即可,
// 即满足 2i+1<=nums.length,推导得出:i <= (nums.length-1)/2
for (int i = (nums.length-1)/2; i>=0; i--){
//从第一个非叶子节点从下至上,从右至左调整结构
adjustHeap(nums, i, nums.length);
}
//2、调整结构
for (int j = nums.length-1; j>=0; j--){
//堆顶元素与末尾元素交换
int temp = nums[0];
nums[0] = nums[j];
nums[j] = temp;
//元素交换之后,数组可能不是大顶堆,需要重新构造大顶堆
adjustHeap(nums, 0, j);
}
}
private static void adjustHeap(int[] nums, int i, int length) {
//先取出当前元素
int temp = nums[i];
//从i的左子节点开始,也就是2i+1
for (int k = i*2+1; k<length; k=2*k+1){
//如果左子节点小于右子节点,k指向右子节点
if (k+1<length && nums[k]<nums[k+1]){
k++;
}
//如果子节点大于父节点,将子节点的值给父节点(不用交换):
// 不用替换的原因,子节点的子节点可能还比他大,需要再次赋值,
// 这种依次赋值,需要用到之前那个最大值,
// 另外,只需要最后将temp那个值给到最后那个位置即可
if (nums[k] > temp){
nums[i] = nums[k]; i = k;
} else {
break;
}
}
//将temp放到最终的位置
nums[i] = temp;
}
5、插入排序
说明:
- 直接插入排序基本思想是每一步将一个待排序的记录,插入到前面已经排好序的有序序列中去,直到插完所有元素为止
步骤
步骤拆解
我们以一个未排序的数组为例。
插入排序比较前两个元素。
它发现14和33都已按升序排列。 目前,14位于已排序的子列表中。
插入排序向前移动并将33与27进行比较。
并发现33不在正确的位置。
它将33与27交换。它还检查已排序子列表的所有元素。 在这里,我们看到排序的子列表只有一个元素14,而27大于14.因此,排序的子列表在交换后仍然排序。
到目前为止,我们在已排序的子列表中有14和27。 接下来,它将33与10进行比较。
这些值不是按排序顺序排列的。
所以我们交换它们。
但是,交换使27和10未分类。
因此,我们也交换它们。
我们再次以未排序的顺序找到14和10。
我们再次交换它们。 到第三次迭代结束时,我们有一个包含4个项目的已排序子列表。
代码:
public static void main(String[] args) {
int[] nums = {1,2,5,2,3,7,5,4,9};
sortAsc(nums);
System.out.println(Arrays.toString(nums));
}
private static void sortAsc(int[] nums) {
for (int i=1; i < nums.length-1; i++) {
//如果取出的元素比他前一个元素小,
//则需要继续往前比较,知道找到比他大的元素位置,
//然后插入到比他大的元素前面
//compare and swap
//当前值比前面的值小
if (nums[i] < nums[i-1]) {
int j; int temp = nums[i];
//不断往前找,直到比他小为止,
//比他小的那个数nums[j]后面位置即可放置当前值
for (j = i-1; j >= 0 && nums[j] > temp; j--) {
nums[j+1] = nums[j];
}
//此时,由于吧i位置的数一直挪,挪到j+1的位置了,
//但是j位置的数是和j+1保持一致的,
// 需要重新给j位置赋值
nums[j+1] = temp;
}
//否则,他两顺序是对的,不需要处理
}
}
说明:
- 冒泡排序就是从序列中的第一个元素开始,依次对相邻的两个元素进行比较,如果前一个元素大于后一个元素则交换它们的位置。如果前一个元素小于或等于后一个元素,则不交换它们;这一比较和交换的操作一直持续到最后一个还未排好序的元素为止。
步骤:
- 比较待排序序列中相邻的两个元素,如果发现左边的元素比右边的元素大,则交换这两个元素
步骤拆解:
代码:
public static void main(String[] args) {
int[] nums = {1,5,3,2,9,3,5,6};
sortAsc(nums); System.out.println(Arrays.toString(nums));
}
private static void sortAsc(int[] nums){
int temp;
for (int i = 0; i < nums.length; i++) {
//相邻2个元素比较,将最大值传递到尾部
for (int j = 0; j < nums.length-1-i; j++) {
//compare and swap
if (nums[j] > nums[j+1]) {
temp = nums[j];
nums[j] = nums[j+1];
nums[j+1] = temp;
}
}
}
}
7、希尔排序
说明:
插入排序经过改进后的一个高效版本
步骤:
步骤拆解
- 1、step = 4
- 2、step = 2
- 3、step=1
- 至此,排序完成
代码:
public static void main(String[] args) {
int[] nums = {1,2,2,5,4,3,765,34,65};
sortAsc(nums);
System.out.println(Arrays.toString(nums));
}
private static void sortAsc(int[] nums) {
//gap为步长, 每次为原来的一半
for (int gap = nums.length/2; gap > 0; gap/=2) {
//对每一组都执行插入排序
for (int i = 0; i<gap; i++){
for (int j = i+gap; j<nums.length;j+=gap) {
//compare and swap
if (nums[j] < nums[j-gap]) {
int k;
int temp = nums[j];
for (k = j-gap; k>=0 && nums[k] > temp; k-= gap) {
nums[k+gap] = nums[k];
}
nums[k+gap] = temp;
}
}
}
}
}
参考
@夜深人静写算法
@学到牛牛
@感觉来了
@嵌入式linux