八大排序算法
目录
注意:以下排序均属于内部排序
(1)插入排序
(2)交换排序
(3)选择排序
(4)归并排序
(5)基数排序
排序总结分析表

一、插入排序

1. 直接插入排序
(1)整体思路
通过动图可以形象的理解到
1. 抽出一张牌,和当前元素之前的元素逐个比较,如果比当前元素小,该元素就后移,直到找到插入位置
2. 在无序序列中抽取一张牌,通过比较插入到有序序列中
说明:(惯用思想)初始时视第一个元素是有序的,之后通过排序逐渐增大有序序列的长度
(2)代码实现
第一种写法:while 循环(更规范)
public static void insert_sort(int[] arr) {
for (int i = 1; i < arr.length; i++) {
int insertvalue = arr[i];
int j = i - 1;
while (j >= 0 && insertvalue < arr[j]) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = insertvalue;
}
}
第二种写法:使用for 循环
public static void insert_sort_1(int[] arr) {
for (int i = 1; i < arr.length; i++) {
int insertvalue = arr[i];
int j = i - 1;
for (; j >= 0; j--) {
if (insertvalue < arr[j]) {
arr[j + 1] = arr[j];
}
else{
break;
}
}
arr[j + 1] = insertvalue;
}
}
2. 折半插入排序
改进点:使用折半查找提高效率,使用循环遍历逐个匹配的效率太差
public static void binary_insert_sort(int[] arr) {
for (int i = 1; i < arr.length; i++) {
int insertvalue = arr[i];
int left = 0;
int right = i - 1;
while (left <= right) {
int mid = left + ((right - left) / 2);
if (arr[mid] < insertvalue) {
left = mid + 1;
} else {
right = mid - 1;
}
}
for (int j = i - 1; j >= right + 1; j--) {
arr[j + 1] = arr[j];
}
arr[right + 1] = insertvalue;
}
}
3. 希尔排序
动图演示

使用间隔 gap,gap 逐渐递减,最后 gap 的值必须是一,每一轮排序对 gap 产生的序列进行排序
快速写希尔排序:把直接插入排序中 “ 1 ” 的位置全部替换成 “ gap ”
public static void shell_sort(int[] arr){
int gap = arr.length / 2;
while (gap >= 1) {
shell(arr, gap);
gap /= 2;
}
}
public static void shell(int[] arr, int gap){
for (int i = gap; i < arr.length; i++) {
int insertvalue = arr[i];
int j = i - gap;
while(j >= 0 && insertvalue < arr[j]){
arr[j + gap] = arr[j];
j -= gap;
}
arr[j + gap] = insertvalue;
}
}
二、交换排序
1. 冒泡排序
动图演示

(1)普通版本
public static void bubble_sort(int[] arr){
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if(arr[j] > arr[j + 1]){
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
(2)改进版本
亮点:通过变量标记的方式标记是否执行了交换操作,可以一定程度上减少比较次数
public static void bubble_sort_improve(int[] arr){
for (int i = 0; i < arr.length - 1; i++) {
int flag = 0;
for (int j = 0; j < arr.length - 1 - i; j++) {
if(arr[j] > arr[j + 1]){
flag = 1;
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
if(flag == 0){
break;
}
}
}
2. 快速排序
动图演示

主要思想:递归,双指针(对撞指针)
思路分析
把第一个元素当作枢纽,然后通过两个指针 left 和 right
left : 在前面找比枢纽大的元素搬到枢纽的后面
right : 在后面找比枢纽小的元素搬到枢纽的前面
递归左右子区间
public static int partition(int[] arr, int left, int right) {
int pivot = arr[left];
while (left < right) {
while (arr[right] >= pivot && left < right) {
right--;
}
arr[left] = arr[right];
while (arr[left] <= pivot && left < right) {
left++;
}
arr[right] = arr[left];
}
arr[left] = pivot;
return left;
}
public static void quicksort(int[] arr, int left, int right) {
if(left < right){
int pivot = partition(arr,left,right);
quicksort(arr,left,pivot - 1);
quicksort(arr,pivot + 1,right);
}
}
三、选择排序
1. 简单选择排序
动图演示

基本思路:选一个数,在这个数的后面找有没有比本身还小的,有就交换位置
优化点:在后面找一个最小的数,避免重复的覆盖,减少比较次数
区别冒泡排序的优化
冒泡排序中是比较相邻两个元素之间是否有序,如果有序就不交换位置
选择排序中是在后面找一个比本身小的数,然后交换位置,为了避免重复的覆盖,需要找到一个最小的数进行交换
(1)普通版本
public static void select_sort(int[] arr){
for (int i = 0; i < arr.length; i++) {
for (int j = i + 1; j < arr.length; j++){
if(arr[j] < arr[i]){
int temp = arr[j];
arr[j] = arr[i];
arr[i] = temp;
}
}
}
}
(2)改进版本
public static void select_sort_improve(int[] arr){
for (int i = 0; i < arr.length; i++) {
int min_index = i;
for (int j = i + 1; j < arr.length; j++){
if(arr[j] < arr[min_index]){
min_index = j;
}
}
if(min_index != i){
int temp = arr[i];
arr[i] = arr[min_index];
arr[min_index] = temp;
}
}
}
2. 堆排序(二叉堆)
(1)基本介绍
堆的性质是符合完全二叉树的,在结构上是递归定义的树结构
重要性质回顾
1. 若一个非叶子节点节点的物理位置为 “ i ”
(1)左孩子
物理位置:“ 2i ”
数组下标:“ 2i + 1 ”
(2)右孩子
物理位置:“ 2i + 1 ”
数组下标:“ 2i + 2 ”
2. 若一个数组的元素可以构成一棵树,数组大小为 n
(1)最后一个元素(叶子节点)在树中的标号对应的数组下标是n / 2
(2)从后往前遍历的第一个非叶子节点在树中的标号对应的数组下标是[n / 2] - 1
补充:关于树中节点的序号和数组下标的关系
方法:从左到右,从上到下依次标号
图例(对应数组下标)
0
/ \
1 2
/ \ / \
3 4 5 6
分类
大根堆:对于完全二叉树中的任意一个节点,其值都大于或等于其子树中所有节点的值。这意味着大根堆的根节点是堆中最大的元素
小根堆:完全二叉树中任意一个节点的值都小于或等于其子树中所有节点的值,即小根堆的根节点是堆中最小的元素
大根堆示例
10
/ \
8 6
/ \ / \
5 3 2
小根堆示例
1
/ \
3 4
/ \ / \
6 8 9
(2)堆排序思想
1. 构建一个大根堆
从后面的=第一个非叶子节点(下标:n / 2 - 1)开始,依次递归的往上调整,使得整棵树形成大根堆的结构
2. 交换最大和最小的节点,重新调整堆
交换思想:交换的过程就是排序的过程,把最大的和最小的交换,即把最大的放入有序区,数组从后往前依次递减排序
调整堆思想:先调整一颗树的左右孩子和根节点的大小关系,构成大根堆后,采用递归思想,递归调整左右子树
理解:在调整过程中,指向的最大根节点会发生变化,对根节点调整即可实现对左右子树的调整)
3. 重复 2 的操作( n 个元素进行 n - 1
趟排序)
关于n - 1的说明
(1)进行n - 1 趟排序
(2)循环的起始变量,对应数组中最后的那个元素,刚好可以和第一个元素完成交换操作(最大的和最小的交换)
(3)循环过程中,刚好对应堆的大小的变化,每一轮排序一个元素(交换的过程),堆中需要调整的元素总数减小一
堆排序代码
public static void heapify(int[] arr, int n, int i) {
int max_index = i;
int left = 2 * i + 1;
int right = 2 * i + 2;
if (left < n && arr[left] > arr[max_index]) {
max_index = left;
}
if (right < n && arr[right] > arr[max_index]) {
max_index = right;
}
if (max_index != i) {
int temp = arr[i];
arr[i] = arr[max_index];
arr[max_index] = temp;
heapify(arr, n, max_index);
}
}
public static void heap_sort(int[] arr) {
int n = arr.length;
for (int i = n / 2 - 1; i >= 0; i--) {
heapify(arr, n, i);
}
for (int i = n - 1; i > 0; i--) {
int temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
heapify(arr, i, 0);
}
}
四、归并排序
动图演示

思路:运用合并为有序序列的思想、递归思想,两个有序序列通过比较的方式合并成一个更长的有序序列,而比较的过程正好是排序的过程
小结:排序左区间,排序右区间,两个区间合并,然而排序的过程又是两个区间合并的过程,即采用递归思想
归并排序代码
五、基数排序