冒泡排序
思路:
每次比较两个, 把大的往后面拿, 这样每次过一遍数组,一定会有一个乱序数组中的最大值到后面
复杂度:
时间复杂度为1+2+3+4+…+n-1 则为O(n2)
空间复杂度为: O(1)
算法不足:
多次重复比较
对比的跨越步太小
public class BubblingSort1 {
public static void main(String[] args) {
int[] a ={4,2,3,1};
bubblingSort(a);
}
public static void bubblingSort(int[] array){
for (int i=0; i<array.length-1;i++){
//大的数都往侯娜
for (int j=0; j<array.length-1-i;j++){
if (array[j]>array[j+1]){
int temp= array[j];
array[j]=array[j+1];
array[j+1]=temp;
}
}
for (int i1 : array) {
System.out.print(i1+" ");
}
System.out.println();
}
}
}
选择排序
思路:
1.首先会在一个数组定义两个区间, 一个有序区间, 一个无序区间, 分别用一个数组下标来标记
2.一开始把第一个数当做成有序区间的数, 并用这个值和后面的比较,如果能够找到一个比现在还小的一个数, 先获取数组下标;
3.最小数的下标和当前的下标对比, 如果不相等就可以交换;
复杂度:
时间复杂度: 因为有两层for循环, 并且没有什么比较可以省略, 也就是说时间稳定性很强,即 O(n2)
空间复杂度: 这里只使用了一个temp, 即O(1);
算法不足:
多次重复比较
public class SelectSort1 {
public static void main(String[] args) {
int i;
int[] a = {20,40,30,10,60,50};
System.out.printf("before sort:");
for (i=0; i<a.length; i++)
System.out.printf("%d ", a[i]);
System.out.printf("\n");
selectSort(a);
System.out.printf("after sort:");
for (i=0; i<a.length; i++)
System.out.printf("%d ", a[i]);
System.out.printf("\n");
}
/**
* @param a 表示的是待排序的数组
*
*
* */
public static void selectSort(int[] a) {
/*
思路:分一个有序数组和一个无序数组, 但是只会有一个交换的变量
* */
int n=a.length; //数组长度
int i; //有序区的末尾位置
int j; //无序区的起始位置
int min; //无序区中最小的元素的【位置】
for (i= 0; i<n;i++){
min=i; //把第一数定义成最小的数
//找出a[i+1] ... a[n] 之间最小的元素, 位置数赋值给min
for (j=i+1; j<n;j++){
//如果后面的数小于前面的数,把最小值的位置赋值给min
if (a[j]<a[min]){
min=j;
}
}
//如果上面有赋值, 那就说明后面的值必定比前面的小,需要进行交换
if(min!=i){
//最小的和当前数组进行交换
int temp=a[i];
a[i]=a[min];
a[min]=temp;
}
}
//算法结束
}
}
插入排序
思路:
1.同选择算法一样, 有一个有序的区域,和一个无序的区域, 不同的是选择无序的数后,用无序的数比较的是有序区域的所有的数, 找到一个位置;
2.位置找到之后, 把当前数需要放的位置的数以及后面的位置到无序区间的前一位后需要向后移动一位
3.第一层for循环使用来选择无序区的数, 第二次for循环是为了移动数组的位置
时间复杂度:
因为同时有两个for循环, 第一个for循环是遍历的无序区间的数, 第二个for循环是为了找到能够放当前选择的无序数, 第三个for循环是为了移动【当前无序数下标的数到能放无序数的后面一个数】的区间; 因为第二个for和第三个for在同一级
时间复杂度为:n*2n 即O(n2)
空间复杂度为: O(1)
算法不足:
需要移动的次数太多,
public class InsertSort1 {
public static void main(String[] args) {
int i;
int[] a = {20,40,30,10,60,50};
System.out.printf("before sort:");
for (i=0; i<a.length; i++)
System.out.printf("%d ", a[i]);
System.out.printf("\n");
insertSort(a);
System.out.printf("after sort:");
for (i=0; i<a.length; i++)
System.out.printf("%d ", a[i]);
System.out.printf("\n");
}
public static void insertSort(int[] array){
int rightNo=1; //无序数组的开始遍历的起始位置
int leftEnd; //用来记录左边有序数组的最后一个
//假定第一个是有序的, 遍历一遍右边无序的数组
for ( rightNo=1 ; rightNo < array.length; rightNo++) {
//array[rightNo]到前面的有序数组找一个可以放下的位置, 找到一个大于前面的值取出有序有序数组的索引
//遍历有序数组的位置, 从右边往左边
for (leftEnd =rightNo-1; leftEnd >=0; leftEnd--){
if (array[leftEnd]<array[rightNo]){
break;
}
}
//找到一个大于的位置, leftEnd+1可以放array[rightNo]这个值
//把需要放的值先拿出来
int temp = array[rightNo];
//此时[leftEnd+1] 到 [rightNo-1] 要移动到, [lefteEnd+2] 到 [rightEnd]的位置
for (int i=rightNo-1;i>=leftEnd+1;i--){
array[i+1]=array[i];
}
//放array[rightNo]到正确位置, 即temp到[leftEnd+1]
array[leftEnd+1]=temp;
}
}
}
希尔排序
思路:
1.先分组, 有一个值叫做步长【步长取值为:3x+1】, 通常为长度的一半; 分组排序之后; 【使用的排序为插入排序】
2.再次分组, 步长为第一次步长为一半
3.需要注意的是插入排序的过程中, 每个分组的无序区是一起排序的, 比如步长为5, 那么第一组的无序区间的第一个为5, 第二组的无序区间第一个为6, 在一次for循环中 , 是把当前步长的所有无序组的第一个进行插入排序;
3.最后的步长都会置值为1; 即退化到插入排序
时间复杂度:
这里面使用了3个for循环,
第一个for循环的遍历条件的是inc/2
, 也就是说循环的次数为log2n ,
第二个for循环, 循环次数为【1/2+1/22+1/23+1/2n】乘以 n , 前n项和为Sn=[1-(1/2)n]* n 即为n
第三个for循环, 是为了移动数组的位置, 1+2+3+4+…+n/2
算法的不足:
算法的时间复杂度很不稳定
public class ShellSort00 {
public static void shell_sort(int[] array){
int n =array.length; // 用来表示数组的长度
int i; //步长区组里面的无序区的第一个元素的下标
int j; //初始值为一个步长, 记录向前的移动和最终赋值的下标
int inc; //步长
int key; //插入的时候临时需要保存的变量
//取数组长度的一半作为步长, 每次循环步长都减少一半来使用
for(inc=n/2; inc>0; inc/=2){
//每一次都选择插入排序
for (i=inc; i<n;i++){//?既然是比较一次, 这里n/2可不可以 //不行,因为这里是向后移动来算, 但是确实也只比较后面的一段
key=array[i]; //这个表示以步长划分的组里面, 无序区元素需要插入的值
//同插入排序一样, 从后面进行排序, 执行条件为【需要插入的key, 比前面的数字小才往前面插入】
//j-=inc; 表示的是步长
//key > array[j-inc] , key大于前面的往前放 即 大到小排序
//key < array[j-inc] , key小于前面的往前放, 一直放到最前面表示最小, 即 小到大排序
for ( j=i ;j>=inc&&key>array[j-inc]; j-=inc){
//
array[j]=array[j-inc];
}
//从无序的第一个到前面的需要放下key的位置这个区间的数都向后后面移动了一个步长, 在步长组里面表示的是向后面移动了一位
array[j]=key;
}
}
}
public static void main(String[] args) {
int i;
int a[] = {80,30,60,40,20,10,50,70};
System.out.printf("before sort:");
for (i=0; i<a.length; i++)
System.out.printf("%d ", a[i]);
System.out.printf("\n");
shell_sort(a);
System.out.printf("after sort:");
for (i=0; i<a.length; i++)
System.out.printf("%d ", a[i]);
System.out.printf("\n");
}
}
快速排序
思路:
1.需要确定一个基准值, 基准值用来分割需要排序的数组;
2.基准值左边的值都是比基准值小的数, 右边都是比基准值大的数
3.选取基准值有两种方式, 一种是取头, 一种是取尾部; 两个方法都差不多; 这里讲解取头
4.定义array[0]为基准值, 还需要定义两个指针, 一个为左边小于基准值的数,组成的区间的指针【left】; 一个为右边大于基准数,组成的区间的指针【right】
5.先使用right指针, 从右边向左边走, 因为右边的值应该都是大于基准值的; 那么就是说, right指针右边先向左边移动是为了找一个比基准值小的数,
代码:
package com.lqq.myself.sortEasy;
/**
* 用途:
*
* @author : lqq
*/
public class QuickSort1 {
/**
* 快速排序基本思路, 先选取一个基准值
* 比这个基准值大的, 移动到基准值的右边,
* 比这个基准值小的, 移动到基准值的左边
*/
/**
*
* @param array 待排序的数组
* @param left 左边待排序的
* @param right 数组的右边界
*/
public static void quickSort(int[] array, int left,int right){
if(left<right){
int l=left; //左边向右边找【大于基准值】的指针
int r=right; //右边向左边找【小于基准值】的指针
int pivot=array[l]; // 把最左边的值作为做一个基准值
while (l<r){
//从右边向左边找
//逻辑是, 【左下标<右下标 且 右边的数组的数大于基准值】则 指针左移动
//跳过循环的条件是,【找到了小于或等于基准值的下标】
while (l<r && array[r]>pivot){
r--;
}
//如果找到了一个小于基准值的下标, 且,左指针位置还在右指针位置的左边
if(l<r){
//找到了左边大于右边的, 则进行交换
array[l]=array[r];
//交换完成之后,左边的指针向右移动一位
l++;
}
//从左向右边找
//前提是左下标小于右下标, 且 , 从左边开始的数组的值小于基准值,则指针右移
//跳出循环的逻辑是, 【找到大于或等于基准值的下标】
while(l<r && array[l]<pivot){
l++;
}
//找到了左边的值大于基准值, 则
if(l<r){
array[r]=array[l];
r--; //右边的指针向左移动一位
}
}
//此时的l和r是相等的
array[r] = pivot;
quickSort(array, left, l -1); /* 递归调用 */
quickSort(array, l +1, right); /* 递归调用 */
}
}
public static void main(String[] args) {
int i;
int a[] = {4,9,3,1,5};
System.out.printf("before sort:");
for (i=0; i<a.length; i++)
System.out.printf("%d ", a[i]);
System.out.printf("\n");
quickSort(a, 0, a.length-1);
System.out.printf("after sort:");
for (i=0; i<a.length; i++)
System.out.printf("%d ", a[i]);
System.out.printf("\n");
}
}
例子说明
待排序的数组: array[]={4,5,3,1,6}
-
while条件为( l < r) , 此时l为0 , r 为
array[].length
-
选择 4 为pivot
-
一开始 【pivot=4】
4 5 3 1 6 l r -
因为
array[r]>pivot
那么直接为r--
; 继续从右往左的while循环【即第一个while循环】 【pivot=4】4 5 3 1 6 l r -
现在第一个while循环被跳过, 因为
array[r]=1<pivot
那么需要把array[r]
的值换到前面去, 即array[l]=array[r]
【pivot=4】1 5 3 1 6 l r 赋值完之后,
l++
, 即左边的指针需要向右移动 【pivot=4】1 5 3 1 6 l r -
从右往左的循环已经结束, 现在开始从左往右; 这里是找一个【找到大于或等于基准值的下标】 【pivot=4】
可以看出
array[l]=5 > (pivote=4)
, 第二个while循环是直接跳过的, 进入if
循环执行
array[r]=array[l];
即把大的值向后面移动1 5 3 5 6 l r 移动完成之后, 将右指针向左边移动一位,
1 5 3 5 6 l r -
while(l<r)
的一次循环结束, 但是现在还是l<r
成立, 再次进入循环 ; 从右边向左边找【找到了小于或等于基准值的下标】当前的右指针指向的值就是符合跳出while的值
(l<r && array[r]>pivot)
, 执行第一个if
array[l]=array[r];
1 3 3 5 6 l r -
//交换完成之后,左边的指针向右移动一位, 下面的条件都无法满足
l<r
一直跳过1 3 3 5 6 r同l
-
while(l<r)
跳出循环, 此时的l=r
把基准值赋值给基准值确切的位置: 【pivot=4】array[r] = pivot;
1 3 4 5 6 r同l
-
此排序只需要执行两次
while(l<r)
循环, 递归向下, 左边需要对比1和3
对比不需要换位置, 右边需要对比5和6
对比不需要换位置,跳出递归,
算法结束
归并排序
思路:
归并有两种, 1.直接把数组分成单个元素, 再组合成一个数组, 需要使用一个数组空间 2. 待排序数组, 二分组内的元素, 直到递归到只有一个元素, 再进行排序
时间复杂度:
把数组二分出来的次数为, 因为二分的条件为:(start+end)/2 即二分次数为:log2n 次,
两个for循环的次数和while次数 (n+(0.5*n)+(0.25 *n )+…) 即 n
时间复杂度为:O(n*log2 n)
因为有一个临时数组的遍历
空间复杂度为: O(n) ,
代码:
/**
* 用途: 归并排序
*
* @author : lqq
*/
public class MergeSort1 {
public static void main(String[] args) {
int i;
int a[] = {80,30,60,40,20,10,50,70};
System.out.printf("before sort:");
for (i=0; i<a.length; i++)
System.out.printf("%d ", a[i]);
System.out.printf("\n");
mergeSortUp2Down(a, 0, a.length-1); // 归并排序(从上往下) 二分组成单个元素
//mergeSortDown2Up(a); // 归并排序(从下往上) 单个元素合并组
System.out.printf("after sort:");
for (i=0; i<a.length; i++)
System.out.printf("%d ", a[i]);
System.out.printf("\n");
}
/**
*
* @param array 待排序的数组
* @param start 数组开始的位置
* @param end 数组结束的位置
*/
public static void mergeSortUp2Down(int[] array, int start , int end){
//初始条件判定
if (array==null || start>=end){
return;
}
//拆分两组的中间值
// 表示待排序数组的中间的值,
// 【表示左边待排序区间的最后一个】
int mid = (start+end)/2;
//左边开始递归调用
mergeSortUp2Down(array,start,mid);
//右边开始递归调用 【mid+1】表示右边的待排序区间的第一个元素
//int i; i=7/2; i=3; 所以需要【mid+1】
mergeSortUp2Down(array,mid+1,end);
//a[start...mid] 和 a[mid...end]是两个有序空间
//将一个数组中的两个相邻有序区间合并成一个
merge(array,start,mid,end);
}
/**
* 两个相邻的有序数组合并成一个
* @param array
* @param arrayStart
* @param arrayMid
* @param arrayEnd
*/
private static void merge(int[] array, int arrayStart, int arrayMid, int arrayEnd) {
//两个相邻的数组, [尾-始+1] 表示新数组需要的长度
int[] tempArray = new int[arrayEnd - arrayStart + 1];
int leftStart=arrayStart; //第一个有序区的起始位置
int rightStart=arrayMid+1; //第二个有序区的起始位置
int tempIndex=0; //临时区域的索引
/*
* 这两个区域逐个比较, 递归到最下面, 就是两个元素的比较
*/
while (leftStart<=arrayMid && rightStart<= arrayEnd){
if (array[leftStart]<=array[rightStart]){
tempArray[tempIndex]=array[leftStart];
tempIndex++; //临时区间向后面移动
leftStart++; //左有序向后面移动
}else{
//如果不是小于等于, 那必然是大于
tempArray[tempIndex]=array[rightStart];
tempIndex++;
rightStart++;
}
//当右边排序的数组都被提取到临时数组
//需要把左边的有序区间的数, 逐一向临时数组移动
while(leftStart<=arrayMid){
tempArray[tempIndex]=array[leftStart];
tempIndex++;
leftStart++;
}
//同上的理由
//需要把右边的有序区间的数, 逐一的向临时数组移动
while(rightStart<=arrayEnd){
tempArray[tempIndex]=array[rightStart];
tempIndex++;
rightStart++;
}
//两个数组已经排序到了临时数组
//临时数组整合到数组array中
//这里复用了leftStar, 赋值0 也可以使用局部变量int i=0
for (leftStart=0;leftStart<tempIndex;leftStart++){
//赋值给待排序数组的开始,原来的数组arrayStart下标开始
array[arrayStart+leftStart]=tempArray[leftStart];
}
}
}
}