排序算法分为两种:比较算法、非比较算法
1、比较算法时间复杂度O(nlogn) ~ O(n^2)
冒泡排序、选择排序、插入排序、归并排序、堆排序
快速排序
2、非比较排序O(n)
计数排序、基数排序、桶排序
特点:排序算法的稳定性:如果Ai = Aj,排序前Ai在Aj之前,排序后Ai还在Aj之前,则称这种排序算法是稳定的
排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,前一个键排序的结果可以为后一个键排序所用
1)冒泡排序
冒泡排序算法的运作如下:
比较相邻的元素,如果前一个比后一个大,就把它们两个调换位置。
对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
针对所有的元素重复以上的步骤,除了最后一个。
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
#include <stdio.h>
// 分类 -------------- 内部比较排序
// 数据结构 ---------- 数组
// 最差时间复杂度 ---- O(n^2)
// 最优时间复杂度 ---- 如果能在内部循环第一次运行时,使用一个旗标来表示有无需要交换的可能,可以把最优时间复杂度降低到O(n)
// 平均时间复杂度 ---- O(n^2)
// 所需辅助空间 ------ O(1)
// 稳定性 ------------ 稳定
public class ArraySortTest {
public void sort(int[] n) {
//第一个for循环表示第几次循环
for(int i=1;i<n.length;i++) {
//第二个for循环表示该次循环进行几次比较
for(int j=0;j<n.length-i;j++) {
//判断比较是否进行换位
if(n[j]>n[j+1]) {
int temp = n[j];
n[j] = n[j+1];
n[j+1] = temp;
}
}
print(n);
}
}
public void print(int[] n) {
for(int i=0;i<n.length;i++)
//输出排完序的数组
System.out.print(n[i] + "\t");
System.out.println();
}
public static void main(String[] args) {
ArraySortTest s = new ArraySortTest();
//此处为需要排序的数组
int[] n = {100,60,80,90,75,38};
s.sort(n);
s.print(n);
}
}
冒泡排序的改进方法:鸡尾酒排序
public class Sad {
public static void main(String[] args) {
int []array= {32,21,54,64,12,9,89,34};
for(int i=0;i<array.length/2;i++)
{
for(int j=0;j<array.length-i-1;j++) {
if(array[j]>array[j+1])
{
int temp=array[j];
array[j]=array[j+1];
array[j+1]=temp;
}
}
for(int j=array.length-i-1;j>i;j--) {
if(array[j]<array[j-1])
{
int temp=array[j];
array[j]=array[j-1];
array[j-1]=temp;
}
}
}
for(int i=0;i<array.length;i++) {
System.out.println(array[i]);
}
}
}
2)选择排序
选择排序也是一种简单直观的排序算法。它的工作原理很容易理解:初始时在序列中找到最小(大)元素,放到序列的起始位置作为已排序序列;然后,再从剩余未排序元素中继续寻找最小(大)元素,放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
#include <stdio.h>
// 分类 -------------- 内部比较排序
// 数据结构 ---------- 数组
// 最差时间复杂度 ---- O(n^2)
// 最优时间复杂度 ---- O(n^2)
// 平均时间复杂度 ---- O(n^2)
// 所需辅助空间 ------ O(1)
// 稳定性 ------------ 不稳定
void Swap(int A[], int i, int j)
{
int temp = A[i];
A[i] = A[j];
A[j] = temp;
}
void SelectionSort(int A[], int n)
{
for (int i = 0; i < n - 1; i++) // i为已排序序列的末尾
{
int min = i;
for (int j = i + 1; j < n; j++) // 未排序序列
{
if (A[j] < A[min]) // 找出未排序序列中的最小值
{
min = j;
}
}
if (min != i)
{
Swap(A, min, i); // 放到已排序序列的末尾,该操作很有可能把稳定性打乱,所以选择排序是不稳定的排序算法
}
}
}
int main()
{
int A[] = { 8, 5, 2, 6, 9, 3, 1, 4, 0, 7 }; // 从小到大选择排序
int n = sizeof(A) / sizeof(int);
SelectionSort(A, n);
printf("选择排序结果:");
for (int i = 0; i < n; i++)
{
printf("%d ", A[i]);
}
printf("\n");
return 0;
}
3)插入排序
具体算法描述如下:
从第一个元素开始,该元素可以认为已经被排序
取出下一个元素,在已经排序的元素序列中从后向前扫描
如果该元素(已排序)大于新元素,将该元素移到下一位置
重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
将新元素插入到该位置后
重复步骤2~5
#include <stdio.h>
// 分类 ------------- 内部比较排序
// 数据结构 ---------- 数组
// 最差时间复杂度 ---- 最坏情况为输入序列是降序排列的,此时时间复杂度O(n^2)
// 最优时间复杂度 ---- 最好情况为输入序列是升序排列的,此时时间复杂度O(n)
// 平均时间复杂度 ---- O(n^2)
// 所需辅助空间 ------ O(1)
// 稳定性 ------------ 稳定
void InsertionSort(int A[], int n)
{
for (int i = 1; i < n; i++) // 类似抓扑克牌排序
{
int get = A[i]; // 右手抓到一张扑克牌
int j = i - 1; // 拿在左手上的牌总是排序好的
while (j >= 0 && A[j] > get) // 将抓到的牌与手牌从右向左进行比较
{
A[j + 1] = A[j]; // 如果该手牌比抓到的牌大,就将其右移
j--;
}
A[j + 1] = get; // 直到该手牌比抓到的牌小(或二者相等),将抓到的牌插入到该手牌右边(相等元素的相对次序未变,所以插入排序是稳定的)
}
}
int main()
{
int A[] = { 6, 5, 3, 1, 8, 7, 2, 4 };// 从小到大插入排序
int n = sizeof(A) / sizeof(int);
InsertionSort(A, n);
printf("插入排序结果:");
for (int i = 0; i < n; i++)
{
printf("%d ", A[i]);
}
printf("\n");
return 0;
}
插入排序的改进:二分插入排序
#include <stdio.h>
// 分类 -------------- 内部比较排序
// 数据结构 ---------- 数组
// 最差时间复杂度 ---- O(n^2)
// 最优时间复杂度 ---- O(nlogn)
// 平均时间复杂度 ---- O(n^2)
// 所需辅助空间 ------ O(1)
// 稳定性 ------------ 稳定
void InsertionSortDichotomy(int A[], int n)
{
for (int i = 1; i < n; i++)
{
int get = A[i]; // 右手抓到一张扑克牌
int left = 0; // 拿在左手上的牌总是排序好的,所以可以用二分法
int right = i - 1; // 手牌左右边界进行初始化
while (left <= right) // 采用二分法定位新牌的位置
{
int mid = (left + right) / 2;
if (A[mid] > get)
right = mid - 1;
else
left = mid + 1;
}
for (int j = i - 1; j >= left; j--) // 将欲插入新牌位置右边的牌整体向右移动一个单位
{
A[j + 1] = A[j];
}
A[left] = get; // 将抓到的牌插入手牌
}
}
int main()
{
int A[] = { 5, 2, 9, 4, 7, 6, 1, 3, 8 };// 从小到大二分插入排序
int n = sizeof(A) / sizeof(int);
InsertionSortDichotomy(A, n);
printf("二分插入排序结果:");
for (int i = 0; i < n; i++)
{
printf("%d ", A[i]);
}
printf("\n");
return 0;
}
对于插入排序,如果比较操作的代价比交换操作大的话,可以采用二分查找法来减少比较操作的次数,我们称为二分插入排序
插入排序的更高效方法:希尔排序 递减增量排序
public class shellSort {
public static void main(String[] args) {
int[] arr = {5, 1, 7, 3, 1, 6, 9, 4};
shellSort(arr);
for (int i : arr) {
System.out.print(i + "\t");
}
}
private static void shellSort(int[] arr) {
//step:步长
for (int step = arr.length / 2; step > 0; step /= 2) {
//对一个步长区间进行比较 [step,arr.length)
for (int i = step; i < arr.length; i++) {
int value = arr[i];
int j;
//对步长区间中具体的元素进行比较
for (j = i - step; j >= 0 && arr[j] > value; j -= step) {
//j为左区间的取值,j+step为右区间与左区间的对应值。
arr[j + step] = arr[j];
}
//此时step为一个负数,[j + step]为左区间上的初始交换值
arr[j + step] = value;
}
}
}
}
4)计数排序
拿上图这个数组来说,元素5之前有8个元素小于等于5(含5本身),因此我排序后5所在的位置肯定是8.所以我只要构造一个(k+1)大小的数组,里面存下所有对应A中每个元素之前的元素个数,理论上就能在线性时间内完成排序。
public class CountSort {
public static void main(String[] args) {
int[] A=new int[]{2,5,3,0,2,3,0,3};
int[] B=countSort(A, 5);
for(int i=0;i<A.length;i++)
{
System.out.println((i+1)+"th:"+B[i]);
}
}
public static int[] countSort(int[] A,int k){
int[] B=new int[A.length];
int[] C=new int[k+1];
int temp=0;
for(int i=0;i<A.length;i++){
C[A[i]]+=1;
}
for(int i=0;i<k+1;i++){
temp=temp+C[i];
C[i]=temp;
}
for(int i=A.length-1;i>0;i--){
B[C[A[i]]-1]=A[i];
C[A[i]]--;
}
return B;
}
}
5)基数排序,桶排序
初始化:构造一个10*n的二维数组,一个长度为n的数组用于存储每次位排序时每个桶子里有多少个元素。
循环操作:从低位开始(我们采用LSD的方式),将所有元素对应该位的数字存到相应的桶子里去(对应二维数组的那一列)。然后将所有桶子里的元素按照桶子标号从小到大取出,对于同一个桶子里的元素,先放进去的先取出,后放进去的后取出(保证排序稳定性)。这样原数组就按该位排序完毕了,继续下一位操作,直到最高位排序完成。
```java
package com.kai.springboot04webcrud.controller;
/**
* Create by kai on 2020/4/14 17:00
*/
public class RadixSort {
public static void main(String[] args)
{
int[] A=new int[]{73,22, 93, 43, 55, 14, 28, 65, 39, 81};
radixSort(A, 100);
for(int num:A)
{
System.out.println(num);
}
}
public static void radixSort(int[] array,int d){
int n=1;
int k=0;
int[][] bucket=new int[10][array.length];
int[] order=new int[array.length];
while(n<d){
for(int i=0;i<array.length;i++){
int digit=(array[i]/n)%10;
bucket[digit][order[digit]]=array[i];
order[digit]++;
}
for(int i=0;i<array.length;i++){
if(order[i]!=0){
for(int j=0;j<order[i];j++){
array[k]=bucket[i][j];
k++;
}
}
order[i]=0;
}
n*=10;
k=0;
}
}
}
本文介绍了排序算法的两大类别:比较算法和非比较算法。比较算法包括冒泡排序、选择排序、插入排序及其改进方法,以及归并排序和堆排序。非比较算法涉及计数排序、基数排序和桶排序,它们能在特定条件下实现线性时间复杂度。文章详细阐述了各个算法的原理和优化技巧,如冒泡排序的鸡尾酒排序、插入排序的二分插入和希尔排序。
5万+

被折叠的 条评论
为什么被折叠?



