十种常见排序算法可以分为两大类:
冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
1.1算法描述
package sort;
import java.util.Arrays;
public class test1
{
public static void main(String[] args)
{
int arr[] =
{
3, 9, -1, 8, 5
};
//测试冒泡排序
// System.out.println("普通版本");
// bubbleSort(arr);
System.out.println("升级版本");
optimizeBubbleSort(arr);
}
//普通版冒泡排序 时间复杂度 O(n^2)
public static void bubbleSort(int[] arr)
{
int temp = 0; // 临时变量
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])
{
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
System.out.println("第" + (i + 1) + "趟排序后的数组");
System.out.println(Arrays.toString(arr));
}
}
//优化版冒泡排序
public static void optimizeBubbleSort(int[] arr)
{
// 冒泡排序 的时间复杂度 O(n^2), 自己写出
int temp = 0; // 临时变量
boolean flag = false; // 标识变量,表示是否进行过交换
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])
{
flag = true;
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
System.out.println("第" + (i + 1) + "趟排序后的数组");
System.out.println(Arrays.toString(arr));
if (!flag)
{ // flag为false,说明在一趟排序中没有进入交换环节 --->已经有序
break;
}
else
{
flag = false; // 重置flag!!!, 进行下次判断
}
}
}
}
1.4结果分析
普通版本:对于每一个无序数组都需要进行n-1趟排序
优化版本:当中途某一趟没有进行任何交换,说明数组已经有序了,排序完成
选择排序(Selection-sort)是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
2.1算法描述
n个记录的直接选择排序可经过n-1趟直接选择排序得到有序结果。具体算法描述如下:
2.3代码实现
//选择排序
public static void selectSort(int[] arr)
{
//在推导的过程,我们发现了规律,因此,可以使用for来解决
//选择排序时间复杂度是 O(n^2)
for (int i = 0; i < arr.length - 1; i++)
{
int minIndex = i;
int min = arr[i];
for (int j = i + 1; j < arr.length; j++)
{
if (min > arr[j])//注意如果要求从大到小排序,只修改min < arr[j]
{ // 说明假定的最小值,并不是最小
min = arr[j]; // 重置min
minIndex = j; // 重置minIndex
}
}
// 将最小值,放在arr[0], 即交换
if (minIndex != i)
{
arr[minIndex] = arr[i];
arr[i] = min;
}
System.out.println("第"+(i+1)+"轮后~~");
System.out.println(Arrays.toString(arr));// 1, 34, 119, 101
}
}
2.4算法分析
表现最稳定的排序算法之一,因为无论什么数据进去都是O(n2)的时间复杂度,所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。理论上讲,选择排序可能也是平时排序一般人想到的最多的排序方法了吧。
插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
3.1算法描述
一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:
//插入排序
public static void insertSort(int[] arr)
{
int insertVal = 0;
int insertIndex = 0;
//使用for循环来把代码简化
for (int i = 1; i < arr.length; i++)
{
//定义待插入的数
insertVal = arr[i];
insertIndex = i - 1; // 即arr[1]的前面这个数的下标
// 给insertVal 找到插入的位置
// 说明
// 1. insertIndex >= 0 保证在给insertVal 找插入位置,不越界
// 2. insertVal < arr[insertIndex] 待插入的数,还没有找到插入位置
// 3. 就需要将 arr[insertIndex] 后移
while (insertIndex >= 0 && insertVal < arr[insertIndex])
{
arr[insertIndex + 1] = arr[insertIndex];// arr[insertIndex]
insertIndex--;
}
// 当退出while循环时,说明插入的位置找到, insertIndex + 1
arr[insertIndex + 1] = insertVal;
System.out.println("第" + i + "轮插入");
System.out.println(Arrays.toString(arr));
}
}
3.4 算法分析
插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
1959年Shell发明,第一个突破O(n2)的排序算法,是简单插入排序的改进版。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序。
4.1算法描述
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:
4.3代码实现
1.交换法:
// 希尔排序时, 对有序序列在插入时采用交换法,
// 思路(算法) ===> 代码
public static void shellSort(int[] arr) {
int temp = 0;
int count = 0;
// 根据前面的逐步分析,使用循环处理
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
for (int i = gap; i < arr.length; i++) {
// 遍历各组中所有的元素(共gap组,每组有个元素), 步长gap
for (int j = i - gap; j >= 0; j -= gap) {
// 如果当前元素大于加上步长后的那个元素,说明交换
if (arr[j] > arr[j + gap]) {
temp = arr[j];
arr[j] = arr[j + gap];
arr[j + gap] = temp;
}
}
}
System.out.println("希尔排序第" + (++count) + "轮 =" + Arrays.toString(arr));
}
//以下是每一轮循环的过程分析
/*
// 希尔排序的第1轮排序
// 因为第1轮排序,是将10个数据分成了 5组
for (int i = 5; i < arr.length; i++) {
// 遍历各组中所有的元素(共5组,每组有2个元素), 步长5
for (int j = i - 5; j >= 0; j -= 5) {
// 如果当前元素大于加上步长后的那个元素,说明交换
if (arr[j] > arr[j + 5]) {
temp = arr[j];
arr[j] = arr[j + 5];
arr[j + 5] = temp;
}
}
}
System.out.println("希尔排序1轮后=" + Arrays.toString(arr));//
// 希尔排序的第2轮排序
// 因为第2轮排序,是将10个数据分成了 5/2 = 2组
for (int i = 2; i < arr.length; i++) {
// 遍历各组中所有的元素(共5组,每组有2个元素), 步长5
for (int j = i - 2; j >= 0; j -= 2) {
// 如果当前元素大于加上步长后的那个元素,说明交换
if (arr[j] > arr[j + 2]) {
temp = arr[j];
arr[j] = arr[j + 2];
arr[j + 2] = temp;
}
}
}
System.out.println("希尔排序2轮后=" + Arrays.toString(arr));//
// 希尔排序的第3轮排序
// 因为第3轮排序,是将10个数据分成了 2/2 = 1组
for (int i = 1; i < arr.length; i++) {
// 遍历各组中所有的元素(共5组,每组有2个元素), 步长5
for (int j = i - 1; j >= 0; j -= 1) {
// 如果当前元素大于加上步长后的那个元素,说明交换
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
System.out.println("希尔排序3轮后=" + Arrays.toString(arr));//
*/
}
2.移位法(效率更高,但是难理解)
//对交换式的希尔排序进行优化->移位法
public static void shellSort2(int[] arr) {
// 增量gap, 并逐步的缩小增量
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
// 从第gap个元素,逐个对其所在的组进行直接插入排序
for (int i = gap; i < arr.length; i++) {
int j = i;
int temp = arr[j];
if (arr[j] < arr[j - gap]) {
while (j - gap >= 0 && temp < arr[j - gap]) {
//移动
arr[j] = arr[j-gap];
j -= gap;
}
//当退出while后,就给temp找到插入的位置
arr[j] = temp;
}
}
}
}
4.4 算法分析
希尔排序的核心在于间隔序列的设定。既可以提前设定好间隔序列,也可以动态的定义间隔序列。动态定义间隔序列的算法是《算法(第4版)》的合著者Robert Sedgewick提出的。
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
5.1算法描述
5.3代码实现
package sort;
import java.util.Arrays;
public class MergeSort
{
public static void main(String[] args)
{
int arr[] =
{
8, 4, 5, 7, 1, 3, 6, 2
};
int temp[] = new int[arr.length];//归并排序需要一个额外空间
mergeSort(arr, 0, arr.length - 1, temp);
System.out.println("归并排序后=" + Arrays.toString(arr));
}
//分+合方法
public static void mergeSort(int[] arr, int left, int right, int[] temp)
{
if (left < right)
{
int mid = (left + right) / 2; //中间索引
//向左递归进行分解
mergeSort(arr, left, mid, temp);
//向右递归进行分解
mergeSort(arr, mid + 1, right, temp);
//合并
merge(arr, left, mid, right, temp);
}
}
//合并的方法
/**
*
* @param arr 排序的原始数组
* @param left 左边有序序列的初始索引
* @param mid 中间索引
* @param right 右边索引
* @param temp 做中转的数组
*/
public static void merge(int[] arr, int left, int mid, int right,
int[] temp)
{
int i = left; // 初始化i, 左边有序序列的初始索引
int j = mid + 1; //初始化j, 右边有序序列的初始索引
int t = 0; // 指向temp数组的当前索引
//(一)
//先把左右两边(有序)的数据按照规则填充到temp数组
//直到左右两边的有序序列,有一边处理完毕为止
while (i <= mid && j <= right)
{//继续
//如果左边的有序序列的当前元素,小于等于右边有序序列的当前元素
//即将左边的当前元素,填充到 temp数组
//然后 t++, i++
if (arr[i] <= arr[j])
{
temp[t] = arr[i];
t += 1;
i += 1;
}
else
{ //反之,将右边有序序列的当前元素,填充到temp数组
temp[t] = arr[j];
t += 1;
j += 1;
}
}
//(二)
//把有剩余数据的一边的数据依次全部填充到temp
while (i <= mid)
{ //左边的有序序列还有剩余的元素,就全部填充到temp
temp[t] = arr[i];
t += 1;
i += 1;
}
while (j <= right)
{ //右边的有序序列还有剩余的元素,就全部填充到temp
temp[t] = arr[j];
t += 1;
j += 1;
}
//(三)
//将temp数组的元素拷贝到arr
//注意,并不是每次都拷贝所有
t = 0;
int tempLeft = left; //
//第一次合并 tempLeft = 0 , right = 1 // tempLeft = 2 right = 3 // tL=0 ri=3
//最后一次 tempLeft = 0 right = 7
System.out.println("templeft=" + tempLeft + " right=" + right);//此语句为了方便看出排序过程,可删掉
while (tempLeft <= right)
{
arr[tempLeft] = temp[t];
t += 1;
tempLeft += 1;
}
}
}
5.4 算法分析
归并排序是一种稳定的排序方法。和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(nlogn)的时间复杂度。代价是需要额外的内存空间。
快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
6.1算法描述
快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:
6.2.1动图演示
6.2.2过程图示
6.3代码实现
代码一:
package sort;
import java.util.Arrays;
public class Test6
{
public static void main(String[] args)
{
int[]arr= {-9,78,30,0,23,-567,70};
quickSort(arr, 0, arr.length-1);
System.out.println("arr="+Arrays.toString(arr));
}
public static void quickSort(int[] arr, int left, int right)
{
int l = left; //左下标
int r = right; //右下标
//pivot 中轴值
int pivot = arr[(left + right) / 2];
int temp = 0; //临时变量,作为交换时使用
//while循环的目的是让比pivot 值小放到左边
//比pivot 值大放到右边
while (l < r)
{
//在pivot的左边一直找,找到大于等于pivot值,才退出
while (arr[l] < pivot)
{
l += 1;
}
//在pivot的右边一直找,找到小于等于pivot值,才退出
while (arr[r] > pivot)
{
r -= 1;
}
//如果l >= r说明pivot 的左右两的值,已经按照左边全部是
//小于等于pivot值,右边全部是大于等于pivot值
if (l >= r)
{
break;
}
//交换
temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
//如果交换完后,发现这个arr[l] == pivot值 相等 r--, 前移
if (arr[l] == pivot)
{
r -= 1;
}
//如果交换完后,发现这个arr[r] == pivot值 相等 l++, 后移
if (arr[r] == pivot)
{
l += 1;
}
}
// 如果 l == r, 必须l++, r--, 否则为出现栈溢出
if (l == r)
{
l += 1;
r -= 1;
}
//向左递归
if (left < r)
{
quickSort(arr, left, r);
}
//向右递归
if (right > l)
{
quickSort(arr, l, right);
}
}
}
代码二(与动画对应):
package sort;
import java.util.Arrays;
public class Test6_1
{
public static void main(String[] args)
{
//给出无序数组
int arr[] =
{
72, 6, 57, 88, 60, 42, 83, 73, 48, 85
};
//输出无序数组
System.out.println(Arrays.toString(arr));
//快速排序
quickSort(arr);
//partition(arr,0,arr.length-1);
//输出有序数组
System.out.println(Arrays.toString(arr));
}
public static void quickSort(int[] arr)
{
int low = 0;
int high = arr.length - 1;
quickSort(arr, low, high);
}
private static int partition(int[] arr, int low, int high)
{
//指定左指针i和右指针j
int i = low;
int j = high;
//将第一个数作为基准值。挖坑
int x = arr[low];
//使用循环实现分区操作
while (i < j)
{//5 8
//1.从右向左移动j,找到第一个小于基准值的值 arr[j]
while (arr[j] >= x && i < j)
{
j--;
}
//2.将右侧找到小于基准数的值加入到左边的(坑)位置, 左指针想中间移动一个位置i++
if (i < j)
{
arr[i] = arr[j];
i++;
}
//3.从左向右移动i,找到第一个大于等于基准值的值 arr[i]
while (arr[i] < x && i < j)
{
i++;
}
//4.将左侧找到的打印等于基准值的值加入到右边的坑中,右指针向中间移动一个位置 j--
if (i < j)
{
arr[j] = arr[i];
j--;
}
}
//使用基准值填坑,这就是基准值的最终位置
arr[i] = x;//arr[j] = y;
//返回基准值的位置索引
return i; //return j;
}
private static void quickSort(int[] arr, int low, int high)
{//???递归何时结束
if (low < high)
{
//分区操作,将一个数组分成两个分区,返回分区界限索引
int index = partition(arr, low, high);
//对左分区进行快排
quickSort(arr, low, index - 1);
//对右分区进行快排
quickSort(arr, index + 1, high);
}
}
}