1. 选择排序
选择排序的基本原理:
对于未排序的一组记录,经过第一次比较,找到最大或者最小值,将它与第一个位置交换,然后除去第一个位置的最大或者最小值,再重复上述过程,直到所有记录排序好。
代码如下:
/**
* 描述:选择排序
*
* @author Ye
* @version 1.0
* @date 2021/8/27 20:08
*/
public class TestSort1 {
public static void selectSort(int[] a){
int min;
int temp;
int flag = 0;
for (int i=0;i<a.length;i++){
min = a[i];
for (int j=i+1;j<a.length;j++){
if (a[j]<min){
min = a[j];
flag = j;
}
}
temp = a[i];
a[i] = min;
a[flag] = temp;
}
}
public static void main(String[] args) {
int i = 0;
int[] a = {5,9,63,87,56,25,41,3,12,69};
selectSort(a);
for (i=0;i<a.length;i++){
System.out.print(a[i] + " ");
}
System.out.println();
}
}
运行截图:
2. 插入排序
对于给定的一组记录,初始时假设第一个记录自成一个有序序列,其余记录为无序序列。然后第二个记录按照记录大小,将当前处理的记录插入到其之前的有序序列中,直至最后一个记录到有序序列为止。
代码如下:
/**
* 描述:插入排序
*
* @author Ye
* @version 1.0
* @date 2021/8/27 20:47
*/
public class TestSort2 {
public static void insertSort(int[] a){
if (a != null){
for (int i=1;i<a.length;i++){
int temp = a[i];
int j = i;
if (a[j-1]>temp){
while (j>=1&&a[j-1]>temp){
a[j] = a[j-1];
j--;
}
a[j] = temp;
}
}
}
}
public static void main(String[] args) {
int[] array = {7,3,19,40,4,7,1};
insertSort(array);
for (int i=0;i<array.length;i++){
System.out.println(array[i] + " ");
}
}
}
运行截图:
3. 冒泡排序
冒泡排序顾名思义就是整个过程就像气泡一样往上升,单向冒泡的基本思想是(假设由小到大排序):对于给定的n个记录,从第一个记录开始依次对相邻的两个记录进行比较,当前面的记录大于后面的记录时,交换位置,进行一轮比较和换位后,n个记录中的最大记录将位于第n位;然后对前(n-1)个记录进行第二轮比较;重复该过程直到进行比较的记录只剩一个为止。
代码示例:
/**
* 描述:冒泡排序
*
* @author Ye
* @version 1.0
* @date 2021/8/28 10:15
*/
public class TestSort5 {
public static void Bubble(int[] array){
int temp;
for (int i=0;i<array.length;i++){
for (int j=1;j<array.length-i;j++){
if (array[j-1]>array[j]){
temp = array[j-1];
array[j-1] = array[j];
array[j] = temp;
}
}
}
}
public static void main(String[] args) {
int[] a = {12,5,6,8,9,4,99,56};
Bubble(a);
for (int i=0;i<a.length;i++){
System.out.println(a[i] + " ");
}
}
}
运行截图:
4. 归并排序
归并排序是利用递归与分治技术将数据序列划分成为越来越小的半子表,再对半子表排序,最后再利用递归方法将排序好的半子表合成越来越大的有序序列。
归并排序算法的原理如下:对于给定的一组记录(n个),首先将每两个相邻的长度为1的子序列进行归并,得到n/2(向上取整)个长度为2或1的有序子序列,在将其两两归并,反复此过程,直到得到一个有序序列。
/**
* 描述:归并排序
*
* @author Ye
* @version 1.0
* @date 2021/8/28 14:31
*/
public class TestSort3 {
public static void Merge(int[] array,int p,int q,int r){
int i,j,k,n1,n2;
n1 = q - p + 1;
n2 = r - q;
int[] L = new int[n1];
int[] R = new int[n2];
for (i=0,k=p;i<n1;i++,k++){
L[i] = array[k];
}
for (i=0,k=q+1;i<n2;i++,k++){
R[i] = array[k];
}
for (k=p,i=0,j=0;i<n1&&j<n2;k++){
if (L[i]>R[j]){
array[k] = L[i];
i++;
}else {
array[k] = R[j];
j++;
}
}
if (i<n1){
for (j=i;j<n1;j++,k++){
array[k] = L[j];
}
}
if (j<n2){
for (i=j;i<n2;i++,k++){
array[k] = R[i];
}
}
}
public static void MergeSort(int[] array,int p,int r){
if (p<r){
int q = (p+r)/2;
MergeSort(array,p,q);
MergeSort(array,q+1,r);
Merge(array,p,q,r);
}
}
public static void main(String[] args) {
int i = 0;
int[] a = {8,9,6,5,89,3,34};
int len = a.length;
MergeSort(a,0,len-1);
for (i=0;i<len-1;i++){
System.out.println(a[i] + " ");
}
}
}
运行截图:
5. 快速排序
基本原理:对于一组给定的记录,通过一趟排序后,将原序列分为两部分,其中前一部分的所有记录均比后一部分的所有记录小,然后再依次对前后两部分的记录进行快速排序,递归该过程,直到序列中的所有记录均有序为止。
算法步骤:
- 分解。将输入的序列array[m···n]划分成两个非空子序列array[m···k]和array[k+1···n],使用array[m··k]中任一元素的值不大于array[k+1···n]中任一值。
- 递归求解。通过递归调用快速排序算法分别对array[m···k]和array[k+1···n]进行排序。
- 合并。由于对分解出的两个子序列的排序是就地进行的,所以在array[m···k]和array[k+1···n]都排好序后不需要执行任何array[m···n]就已排好序。
代码示例:
/**
* 描述:快速排序
*
* @author Ye
* @version 1.0
* @date 2021/8/28 18:22
*/
public class TestSort4 {
public static void sort(int[] array,int low,int high){
int i,j;
int index;
if (low >= high){
return;
}
i = low;
j = high;
index = array[i];
while (i < j){
while (i < j && array[j] >= index){
j--;
}
if (i<j){
array[i++] = array[j];
}
while (i<j&&array[i]<index){
i++;
}
if (i<j){
array[j--] = array[i];
}
}
array[i] = index;
sort(array,low,i-1);
sort(array,i+1,high);
}
public static void quickSort(int[] array){
sort(array,0,array.length-1);
}
public static void main(String[] args) {
int i = 0;
int[] a = {5,6,9,8,56,34,25,77};
int len = a.length;
quickSort(a);
for (i=0;i<a.length;i++){
System.out.print(a[i] + " ");
}
}
}
运行截图:
6. 希尔排序
基本原理:
先将待排序的数组元素分成多个子序列,使得每个子序列的元素个数相对较少,然后对各个子序列分别进行直接插入排序,待整个排序序列“基本有序后”,最后再对所有元素进行一次直接插入排序。
具体步骤:
- 选择一个步长序列t1,t2,···,tk,满足ti > tj ( i<j ),tk = 1
- 按步长序列个数k,对待排序序列进行k趟排序
- 每趟排序,根据对应的步长ti,将待排序序列分割成ti个子序列,分别对各个子序列进行直接插入排序。
代码示例:
/**
* 描述:希尔排序
*
* @author Ye
* @version 1.0
* @date 2021/8/28 19:40
*/
public class TestSort6 {
public static void shellSort(int[] array){
int length = array.length;
int i,j;
int h;
int temp;
for (h = length/2;h>0;h=h/2){
for (i = h;i<length;i++){
temp = array[i];
for (j=i-h;j>=0;j-=h){
if (temp<array[j]){
array[j+h] = array[j];
}else {
break;
}
}
array[j+h] = temp;
}
}
}
public static void main(String[] args) {
int i = 0;
int[] a = {1,12,69,23,64,88,99,51};
int len = a.length;
shellSort(a);
for (i=0;i<len;i++){
System.out.println(a[i] + " ");
}
}
}
运行截图:
7. 堆排序
基本原理:
堆排序的思想是对于给定的n个记录,初始时把这些记录看作一棵顺序存储的二叉树,然后将其调整为一个大顶堆,然后将堆的最后一个元素与堆顶元素(二叉树根结点)进行交换后,堆的最后一个记录即为最大记录;接着将前(n-1)个元素(即不包括最大记录)重新调整为一个大顶堆,再将堆顶元素与当前堆的最后一个元素进行交换后得到次大的记录,重复该过程直到调整的堆中只剩一个元素时为止,该元素即为最小记录,此时可得到一个有序序列。
主要过程:
- 构建堆
- 交换堆顶元素与最后一个元素的位置
示例代码:
/**
* 描述:堆排序
*
* @author Ye
* @version 1.0
* @date 2021/8/28 21:21
*/
public class TestSort7 {
public static void adjustMinHeep(int[] a,int pos,int len){
int temp;
int child;
for (temp = a[pos];2*pos +1 <= len;pos = child){
child = 2 * pos + 1;
if (child<len && a[child] > a[child + 1]){
child++;
}
if (a[child] < temp){
a[pos] = a[child];
}else {
break;
}
}
a[pos] = temp;
}
public static void myMinHeapSort(int[] array){
int i;
int len = array.length;
for (i = len/2-1;i >= 0;i--){
adjustMinHeep(array,i,len-1);
}
for (i = len - 1;i >= 0;i--){
int temp = array[0];
array[0] = array[i];
array[i] = temp;
adjustMinHeep(array,0,i-1);
}
}
public static void main(String[] args) {
int i = 0;
int[] a = {89,63,54,12,35,95,64,19,39,88};
int len = a.length;
myMinHeapSort(a);
for (i = 0; i<len;i++){
System.out.println(a[i] + " ");
}
}
}
运行截图:
总结
算法性能对比
排序算法 | 最好时间 | 平均时间 | 最坏时间 | 辅助存储 | 稳定性 | 备注 |
---|---|---|---|---|---|---|
简单选择排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 不稳定 | n小时比较好 |
直接插入排序 | O(n) | O(n^2) | O(n^2) | O(1) | 稳定 | 大部分已有序时较好 |
冒泡排序 | O(n) | O(n^2) | O(n^2) | O(1) | 稳定 | n小时比较好 |
希尔排序 | O(n) | O(nlogn) | O(n^s) 1<s<2 | O(1) | 不稳定 | s是所选分组 |
快速排序 | O(nlogn) | O(nlogn) | O(n^2) | O(logn) | 不稳定 | n大时比较好 |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 不稳定 | n大时比较好 |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 | n大时比较好 |
结论:
- 简单地说,所有相等的数经过某种排序方法后,仍能保持它们在排序之前的相对次序,就称这种排序方法是稳定的,反之,就是非稳定的,例如,一组数排序前是 a1,a2,a3,a4,a5,其中 a2 =a4,经过某种排序后 a1,a2,a4,a3,a5,则说这种排序是稳定的,因为a2 排序前在 a4 的前面,排序后它还是在 a4 的前面。假如变成 a1,a4,a2,a3,a5 就不是稳定的了。各种排序算法中稳定的排序算法有直接插入排序、冒泡排序和归并排序,而不稳定的排序算法有希尔排序、快速排序、简单选择排序和堆排序。
- 时间复杂度为 O(n^2)的排序算法有直接插入排序、冒泡排序、快速排序和简单选择排序;时间复杂性为 O(nlogn)的排序算法有堆排序和归并排序。
- 空间复杂度为 O(1)的算法有简单选择排序、直接插入排序、冒泡排序、希尔排序和堆排序,空间复杂度为 O(n)的算法是归并排序,空间复杂度为 O(logn)的算法是快速排序。
- 虽然直接插入排序和冒泡排序的速度比较慢,但是当初始序列整体或局部有序时,这两种排序算法会有较高的效率。当初始序列整体或局部有序时,快速排序算法的效率会下降。当排序序列较小且不要求稳定性时,直接选择排序效率较好;要求稳定性时,冒泡排序效率较好。
参考:《Java程序员面试笔试宝典》 何昊、薛鹏、叶向阳 编著