十种基本排序算法
1.冒泡排序:
思路:两两排序把大的放后边
时间复杂度: O(n²)
空间复杂度:O(1)
int[] arr=new int[]{1,5,8,2,3,9,4};
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;
}
}
}
System.out.println("结果:"+Arrays.toString(arr));
//-----------------------
//两头冒泡法
public void Bubble_2 ( int r[], int n){
int low = 0;
int high= n -1; //设置变量的初始值
int tmp,j;
while (low < high) {
for (j= low; j< high; ++j) //正向冒泡,找到最大者
if (r[j]> r[j+1]) {
tmp = r[j]; r[j]=r[j+1];r[j+1]=tmp;
}
--high; //修改high值, 前移一位
for ( j=high; j>low; --j) //反向冒泡,找到最小者
if (r[j]<r[j-1]) {
tmp = r[j]; r[j]=r[j-1];r[j-1]=tmp;
}
++low; //修改low值,后移一位
}
}
2. 选择排序
思路:选出最小的换前面,时间和冒泡一样,但是交换减少
时间复杂度: O(n²)
空间复杂度:O(1)
int[] arr=new int[]{1,5,8,2,3,9,4};
for (int i = 0; i < arr.length - 1; i++) {//做第i趟排序
int k = i;
for (int j = k + 1; j < arr.length ; j++) {//选最小的记录
if(arr[j] < arr[k]){
k = j; //记下目前找到的最小值所在的位置
}
}
//在内层循环结束,也就是找到本轮循环的最小的数以后,再进行交换
if(i!=k){
int temp = arr[i];
arr[i] = arr[k];
arr[k] = temp;
}
}
System.out.println("结果:"+ Arrays.toString(arr));
3.直接插入排序
将数据插入到已有序的数列中
排序前:将每个元素看成有序的数列
第一趟排序后:得到一个有序数列,其大小为2
第二趟排序后:得到一个有序数列,其大小为3
第三趟排序后:得到一个有序数列,其大小为4
思路:先取一个有序的队列,然后将前面的其他数字一个一个和这个有序数列排序
时间复杂度 最好情况:O(n) 最坏情况O(n²)
空间复杂度 O(n)
int[] arr=new int[]{1,5,8,2,3,9,4};
for (int i = 1; i < arr.length; i++) {
for (int j = i; j >0 ; j--) {
if(arr[j-1] > arr[j]){
int temp = arr[j-1];
arr[j-1] = arr[j];
arr[j] = temp;
}
}
}
System.out.println("结果:"+ Arrays.toString(arr));
int[] arr=new int[]{1,5,8,2,3,9,4};
for (int i = 1; i < arr.length; i++) {
int temp = arr[i];
int j;
for (j = i; j >0 && arr[j-1]>temp ; j--) {
arr[j] = arr[j-1];
}
arr[j] = temp;
}
System.out.println("结果:"+ Arrays.toString(arr));
4、希尔排序(基于插入排序)
希尔排序(Shell’s Sort)是插入排序的一种,是直接插入排序算法的一种更高版本的改进版本
思路:选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
按增量序列个数k,对序列进行k 趟排序;
每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度
int[] arr=new int[]{1,5,8,2,3,9,4};
//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;
}
}
System.out.println("结果:"+ Arrays.toString(arr));
5、归并排序
思路:将两个(或两个以上)有序表合并成一个新的有序表 即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列
时间复杂度为 O(nlgn)
稳定排序方式
//合并两个数组
public static void main(String[] args) {
int[] num1 = new int[]{1, 2, 4, 6, 7, 123, 411, 5334, 1414141, 1314141414};
int[] num2 = new int[]{0, 2, 5, 7, 89, 113, 5623, 6353, 134134};
//变量用于存储两个集合应该被比较的索引(存入新集合就加一)
int a = 0;
int b = 0;
int[] num3 = new int[num1.length+num2.length];
for (int i = 0; i < num3.length; i++) {
if (a < num1.length && b < num2.length) { //两数组都未遍历完,相互比较后加入
if(num1[a] > num2[b]){
num3[i] = num2[b];
b++;
}else{
num3[i] = num1[a];
a++;
}
} else if(a<num1.length){//a左边还有剩余,直接放入数组
num3[i] = num1[a];
a++;
} else if(b<num2.length){
num3[i] = num2[b];
b++;
}
}
System.out.println("排序后:" + Arrays.toString(num3));
}
//完整的排序
public class MergeSort {
//[l..mid],[mid...r]进行归并
private static void merge(Comparable[] arr,int l,int mid,int r){
//arr[l...r]
Comparable[] aux = Arrays.copyOfRange(arr,l,r+1);
int i=l,j = mid+1;
for (int k = l; k <= r; k++) {
if(i < mid && j < r){
if(aux[i-l].compareTo(aux[j-l])<0){
arr[k] = aux[i - l];
i++;
}else {
arr[k] = aux[j - l];
j++;
}
} else if(i < mid){
arr[k] = aux[i - l];
i++;
} else if(j < r){
arr[k] = aux[j - l];
j++;
}
}
}
private static void sort(Comparable[] arr, int l, int r) {
if(l >= r){
return;
}
int mid = (r+l)/2;
sort(arr,l,mid);
sort(arr,mid+1,r);
merge(arr,l,mid,r);
}
public static void sort(Comparable[] arr) {
int n = arr.length;
sort(arr, 0, n - 1);
}
public static void main(String[] args) {
// Merge Sort是我们学习的第一个O(nlogn)复杂度的算法
// 可以在1秒之内轻松处理100万数量级的数据
// 注意:不要轻易尝试使用SelectionSort, InsertionSort或者BubbleSort处理100万级的数据
// 否则,你就见识了O(n^2)的算法和O(nlogn)算法的本质差异:)
int N = 10000;
Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 10000);
//sort(arr);
SortTestHelper.testSort("com.xt.camel.democamel.web.processor.MergeSort", arr);
System.out.println("排序后:" + Arrays.toString(arr));
}
6、快速排序 (基于划分)
思路:一般选取第一个数作为基数,然后从后往前比较,如果比基数小,就和基数交换,在从左往右比较,如果比基数大,就交换,直到i=j为止,此时,把这个数组按基数为分界,分成两组,在进行上述计算
时间复杂度O(n^2 ) -- O (NlogN)
空间复杂度 O(1)
private static void quickSort(int[] arr,int low,int high){
int i,j,temp,t;
if(low>high){
return;
}
i=low;
j=high;
//temp就是基准位
temp = arr[low];
while (i<j) {
//先看右边,依次往左递减
while (temp<=arr[j]&&i<j) {
j--;
}
//再看左边,依次往右递增
while (temp>=arr[i]&&i<j) {
i++;
}
//如果满足条件则交换
if (i<j) {
t = arr[j];
arr[j] = arr[i];
arr[i] = t;
}
}
//最后将基准为与i和j相等位置的数字交换
arr[low] = arr[i];
arr[i] = temp;
//递归调用左半数组
quickSort(arr, low, j-1);
//递归调用右半数组
quickSort(arr, j+1, high);
}
public static void main(String[] args) {
int[] arr = {10,7,2,4,7,62,3,4,2,1,8,9,19};
quickSort(arr, 0, arr.length-1);
System.out.println("结果:"+ Arrays.toString(arr));
}
7、堆排序
思路:初始时把要排序的n个数的序列看作是一棵顺序存储的二叉树(一维数组存储二叉树),调整它们的存储序,使之成为一个堆,将堆顶元素输出,得到n 个元素中最小(或最大)的元素,这时堆的根节点的数最小(或者最大)。然后对前面(n-1)个元素重新调整使之成为堆,输出堆顶元素,得到n 个元素中次小(或次大)的元素。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。称这个过程为堆排序。
时间复杂度 O(nlgn )
空间复杂度 O(1)
private static int heapSize;
public static void main(String[] args) {
int[] a={1,4,2,7,9,3};
heapSort(a);
System.out.println("结果:"+ Arrays.toString(arr));
}
private static void heapSort(int[] a) {
heapSize = a.length;
buildMaxHeap(a);
for (int i = a.length - 1; i >= 1; i--) {
swap(a, i, 0);
heapSize = heapSize - 1;
maxHeapify(a, 0);
}
}
private static void swap(int[] a, int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
private static void buildMaxHeap(int[] a) {
for (int i = a.length / 2; i >= 0; i--) {
maxHeapify(a, i);
}
}
private static void maxHeapify(int[] a, int i) {
int l = left(i);
int r = right(i);
int largest = i;
if (l < heapSize && a[l] > a[i])
largest = l;
else
largest = i;
if (r < heapSize && a[r] > a[largest])
largest = r;
if (largest != i) {
swap(a, i, largest);
maxHeapify(a, largest);
}
}
private static int left(int i) {
return 2 * i;
}
private static int right(int i) {
return 2 * i + 1;
}
8、计数排序
计数排序的基本思想是对于给定的输入序列中的每一个元素x,确定该序列中值小于x的元素的个数(此处并非比较各元素的大小,而是通过对元素值的计数和计数值的累加来确定)。一旦有了这个信息,就可以将x直接存放到最终的输出序列的正确位置上。例如,如果输入序列中只有17个元素的值小于x的值,则x可以直接存放在输出序列的第18个位置上。当然,如果有多个元素具有相同的值时,我们不能将这些元素放在输出序列的同一个位置上,因此,上述方案还要作适当的修改。牺牲空间换取时间的方法,在某个整数范围内,快于任何算法
public static void main(String[] args) {
int[] arr = {1,4,6,7,5,4,3,2,1,4,5,10,9,10,3};
arr = sortCount(arr);
System.out.println("结果:"+ Arrays.toString(arr));
}
private static int[] sortCount(int[] a){
int cnum =0;//最大值
for(int i = 0 ; i<a.length;i++){
if(cnum<a[i]){
cnum = a[i];
}
}
//构建最大值+1长度的数组
int[] c = new int[cnum+1];
//构建排序后的数组
int[] b = new int[a.length];
for(int i =0;i<c.length;i++){
c[i]=0;
}
//c数组中每个元素的个数,c的下标代表a中的值,c下标的值代表a中的值的个数
for(int i = 0;i<a.length;i++){
c[a[i]]+=1;
}
//c中值变化为:表示A中小于等于自己的一共有多少个数
for(int i =1;i<c.length;i++){
c[i]+=c[i-1];
}
//从末尾遍历A数组,找寻A数组对应的值作为下标在C数组中对应的值。然后该值就是A数组中值在B数组中值的位置
//c中下标的值的个数需要-1
for(int i = a.length-1;i>=0;i--){
b[c[a[i]]-1]=a[i];
c[a[i]]-=1;
}
return b;
}
9、桶排序
思路:是将阵列分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递回方式继续使用桶排序进行排序)。桶排序是鸽巢排序的一种归纳结果。当要被排序的阵列内的数值是均匀分配的时候,桶排序使用线性时间(O(n))。但桶排序并不是 比较排序,他不受到 O(n log n) 下限的影响。
简单来说,就是把数据分组,放在一个个的桶中,然后对每个桶里面的在进行排序。
例如要对大小为[1..1000]范围内的n个整数A[1..n]排序
首先,可以把桶设为大小为10的范围,具体而言,设集合B[1]存储[1..10]的整数,集合B[2]存储 (10..20]的整数,……集合B[i]存储( (i-1)*10, i*10]的整数,i = 1,2,..100。总共有 100个桶。
然后,对A[1..n]从头到尾扫描一遍,把每个A[i]放入对应的桶B[j]中。 再对这100个桶中每个桶里的数字排序,这时可用冒泡,选择,乃至快排,一般来说任 何排序法都可以。
最后,依次输出每个桶里面的数字,且每个桶中的数字从小到大输出,这 样就得到所有数字排好序的一个序列了。
当均匀分布时,空间复杂度接近为0(n) ,空间复杂度O(N+M)
public static void main(String[] args) {
// 输入元素均在 [0, 10) 这个区间内
float[] arr = new float[] { 0.12f, 2.2f, 8.8f, 7.6f, 7.2f, 6.3f, 9.0f, 1.6f, 5.6f, 2.4f };
bucketSort(arr);
System.out.println("结果:"+ Arrays.toString(arr));
}
public static void bucketSort(float[] arr) {
// 新建一个桶的集合
ArrayList<LinkedList<Float>> buckets = new ArrayList<>();
for (int i = 0; i < 10; i++) {
// 新建一个桶,并将其添加到桶的集合中去。
// 由于桶内元素会频繁的插入,所以选择 LinkedList 作为桶的数据结构
buckets.add(new LinkedList<Float>());
}
// 将输入数据全部放入桶中并完成排序
for (float data : arr) {
int index = getBucketIndex(data);
insertSort(buckets.get(index), data);
}
// 将桶中元素全部取出来并放入 arr 中输出
int index = 0;
for (LinkedList<Float> bucket : buckets) {
for (Float data : bucket) {
arr[index++] = data;
}
}
}
//计算得到输入元素应该放到哪个桶内
public static int getBucketIndex(float data) {
// 这里例子写的比较简单,仅使用浮点数的整数部分作为其桶的索引值
// 实际开发中需要根据场景具体设计
return (int) data;
}
//我们选择插入排序作为桶内元素排序的方法 每当有一个新元素到来时,我们都调用该方法将其插入到恰当的位置
public static void insertSort(List<Float> bucket, float data) {
ListIterator<Float> it = bucket.listIterator();
boolean insertFlag = true;
while (it.hasNext()) {
if (data <= it.next()) {
it.previous(); // 把迭代器的位置偏移回上一个位置
it.add(data); // 把数据插入到迭代器的当前位置
insertFlag = false;
break;
}
}
if (insertFlag) {
bucket.add(data); // 否则把数据插入到链表末端
}
}
10、基数排序
(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用,就是从低位到高位基数排序,基数排序法是属于稳定性的排序,其时间复杂度为O (nlog(r)m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。
//arr是要排序的数组,max是数组中最大的数有几位
private static void lsdRadixSort(int[] arr,int max){
//count数组用来计数
int[] count = new int[arr.length];
//bucket用来当桶(在下面你就理解了什么是桶了),放数据,取数据
int[] bucket = new int[arr.length];
//k表示第几位,1代表个位,2代表十位,3代表百位
for(int k=1;k<=max;k++){
//把count置空,防止上次循环的数据影响
for(int i=0;i<arr.length;i++){
count[i] = 0;
}
//分别统计第k位是0,1,2,3,4,5,6,7,8,9的数量
//以下便称为桶
//即此循环用来统计每个桶中的数据的数量
for(int i=0;i<arr.length;i++){
count[getFigure(arr[i],k)]++;
}
//利用count[i]来确定放置数据的位置
for(int i=1;i<arr.length;i++){
count[i] = count[i] + count[i-1];
}
//执行完此循环之后的count[i]就是第i个桶右边界的位置
//利用循环把数据装入各个桶中,注意是从后往前装
//这里是重点,一定要仔细理解
for(int i=arr.length-1;i>=0;i--){
int j = getFigure(arr[i],k);
bucket[count[j]-1] = arr[i];
count[j]--;
}
//将桶中的数据取出来,赋值给arr
for(int i=0,j=0;i<arr.length;i++,j++){
arr[i] = bucket[j];
}
}
}
//此函数返回整型数i的第k位是什么
private static int getFigure(int i,int k){
int[] a = {1,10,100};
return (i/a[k-1])%10;
}
public static void main(String[] args) {
int[] arr = {21,56,88,195,354,1,35,12,6,7};
lsdRadixSort(arr, 3);
System.out.println("结果:"+ Arrays.toString(arr));
}