简单选择排序
冒泡排序是将最大的元素往后面排,简单选择排序是将小的元素往前面排
算法思想:
1.将第一个元素和其余元素进行对比,如果第一个元素和其他元素相比,第一个元素大,则交换,一轮下来,最小的元素会在第一个
2.将第二个元素和其余的元素对比,如果对比结果大,则交换
3.重复
算法图解:
以此类推……
Java代码:
void simpleSort(int[] a){
int temp;
for (int i=0;i<a.length;++i)
{
temp=a[i];
for(int j=i;j<a.length;++j)
{
if (a[j]<temp)
{
temp=ia[j];
swap(a[i],a[j]);
}
}
}
}
冒泡排序
冒泡排序就是把一个数组中最大的数字往数组的末端排序:
算法思想:
1.比较相邻的两个元素,如果第一个元素比第二个元素大,就交换
2.对每一对相邻的元素都执行1操作,从开始第一对到最后一对(所以经过一轮之后,最大的数字会被安排到最末尾的位置放着)
3.第一轮排序之后,因为最后一个数字是最大的,不论如何位置都不会改变的,那么我们就不管最后一个,排除第一个位置之后,我们执行1,2操作
算法图解:
冒泡排序算法是稳定的排序算法:当arr[i]==arr[i+1]时,我们可以选择不移动,所以为稳定的
时间复杂度位O(n^2):双重循环遍历
空间复杂度O(1):只在原本的数组上排序交换
Java代码:
public void bubbleSort(int[] array) {
int n = array.length;
for (int i = 0; i < n - 1; i++) {
//在这里j不需要遍历到n-1了,因为n-1-i~n-1之间的元素已经排好序了,不需要再比较
for (int j = 0; j < n - 1 - i; j++) {
//将最大元素移动到数组末尾
if (array[j] > array[j + 1]) {
//交换
swap(array, j, j + 1);
}
}
}
}
void swap(int[] array, int i, int j) {
array[i] = array[i] + array[j];
array[j] = array[i] - array[j];
array[i] = array[i] - array[j];
}
选择排序
选择排序和冒泡排序时一样的思想,都是把最大的数字排到最后面,不同的是,冒泡排序是对比相邻元素交换,而选择排序是直接将最大的数字和最后一个数字交换。
算法图解:
如此类推;
java代码:
public void selectSort(int [[ nums){
int size = nums.length;
for (int i=0;i<size-1;i++){
int a = i;
for (int j=i+1;j<size;j++){
if (nums[a] > nums[j]){
a = j;
}
}
int temp = nums[a];
nums[a] = nums[i];
nums[i] = temp;
}
}
选择排序是不稳定的排序算法;
时间复杂度:O(n^2)
空间复杂度:O(1)
插入排序
顾名思义就是将元素插入到他属于的位置上。
插入排序非常类似于整扑克牌。在开始摸牌时,左手是空的,牌面朝下放在桌上。接着,一次从桌上摸起一张牌,并将它插入到左手一把牌中的正确位置上。为了找到这张牌的正确位置,要将它与手中已有的牌从右到左地进行比较。无论什么时候,左手中的牌都是排好序的。
算法思想:
1.刚开始结果集只有一个元素
2.获取要插入的元素,和结果集相对比,如果比第一个元素大,则往后继续继续比较,如果比第一个元素小,则放在元素前面。
3.重复2操作直到全部元素排列完毕
算法图解:
Java代码:
public static void insertSort(int[] A) {
int key = 0;
int j = 0;
for (int i = 1; i < A.length; i++) {
//要插入的数
key = A[i];
for (j = i - 1; j >= 0 && key < A[j]; j--) {
A[j + 1] = A[j];
}
A[j + 1] = key;
}
}
插入排序是稳定的排序算法:
时间复杂度:O(n^2)
空间复杂度:O(1)
归并排序的思想:就是将两个有序的数组归并成一个更大的有序数组
那么将两个有序的数组合并成一个大的数组,你会怎么操作呢?
一般我们都是往一个数组里面插入另外一个数组,但是这样会带来插入排序
的一个问题,如果往已经排序好的结果集非常大,那么我们要插入最小元素所要移动的代价就非常大了,那么我们怎么来解决这个问题呢?
原地归并的抽象方法:
该方法先将所有的元素复制到aux[]中,然后再归并到a[]中。方法在指定第二个for循环的时候,用了4个条件判断,分别是左半边用尽(取右半边的元素)、右半边用尽(取左半边的元素)、右半边的元素比左半边的元素小(取右半边的元素)、右半边的元素比左半边的元素大(取左半边的元素)。
public static void merge(int[] a,int low,int mid,int hight) {
int i=low,j=mid+1;
int[] aux=new int[a.length];
//int[] aux=a.clone();
for(int k=low;k<=hight;k++) {
aux[k]=a[k];
}
for(int k=low;k<=hight;k++) {
if(i>mid) {
a[k]=aux[j++];
}else if(j>hight) {
a[k]=aux[i++];
}else if(aux[j]<aux[i]) {
a[k]=aux[j++];
}else {
a[k]=aux[i++];
}
}
}
那么我们有了这样的一个将两个数组合并成一个数组之后,我们就可以将一个大数组划分成n个小数组,然后小数组之间两两使用这个方法,就可以完成归并排序了,我们将这种归并排序称为自顶向下的归并排序
归并排序
自顶向下的归并排序
自顶向下的归并排序使用分治的思想,将大问题不断划分成小问题,小问题解决了,大问题也就解决了。
public static void sortTopToButton(int[] a,int low,int hight) {
if(low>hight) {
return;
}
int mid=low+(low+hight)/2;
//将数组分为左右两边,分别进行归并排序
sortTopToButton(a, low, mid);
sortTopToButton(a, mid+1, hight);
merge(a, low, mid, hight);
}
当然了,我们也可以换个角度想问题,如果我们将数组一开始就两两划分,然后两两组合成我们需要得到的数组,这个种方法我们称之为自底向上的归并排序
自底向上的归并排序
public static void sortButtonToTop(int[] a) {
int N=a.length;
for(int i=1;i<N;i+=i) {
for(int low=0;low<N-i;low+=i+i) {
merge(a, low, low+i-1, Math.min(low+i+i-1, N-1));
}
}
}
package sort;
/**
*@time 2020年4月26日:上午10:38:58
*@author Weirdo
*@version 1.0
**/
public class Merge {
public static void main(String[] args) {
// TODO Auto-generated method stub
}
public static void merge(int[] a,int low,int mid,int hight) {
int i=low,j=mid+1;
int[] aux=new int[a.length];
//int[] aux=a.clone();
for(int k=low;k<=hight;k++) {
aux[k]=a[k];
}
for(int k=low;k<=hight;k++) {
if(i>mid) {
a[k]=aux[j++];
}else if(j>hight) {
a[k]=aux[i++];
}else if(aux[j]<aux[i]) {
a[k]=aux[j++];
}else {
a[k]=aux[i++];
}
}
}
//自顶向下的归并排序
public static void sortTopToButton(int[] a,int low,int hight) {
if(low>hight) {
return;
}
int mid=low+(low+hight)/2;
//将数组分为左右两边,分别进行归并排序
sortTopToButton(a, low, mid);
sortTopToButton(a, mid+1, hight);
merge(a, low, mid, hight);
}
//自底向上的归并排序
public static void sortButtonToTop(int[] a) {
int N=a.length;
for(int i=1;i<N;i+=i) {
for(int low=0;low<N-i;low+=i+i) {
merge(a, low, low+i-1, Math.min(low+i+i-1, N-1));
}
}
}
}
总结自:《算法》(第四版)2.2归并排序,大家可以看一下,收获良多
希尔排序
希尔排序是基于插入排序的快速排序算法。对于大规模乱序数组插入排序很慢,因为他只会交换相邻的元素,因此元素只能一点一点的从数组的一端移动到另一端。如果最小的元素恰好在最末端,那么要挪到正确的位置需要移动n-1次。
希尔排序为了加快速度简单地改进了插入排序,交换不相邻的元素以对数组的局部进行排序,并最终用插入排序将局部有序的数组排序
——《算法》(第四版,2.1.6希尔排序)
根据上面这段,我的理解就是,希尔排序就是通过一种手段,将原本的数组状态1转换成为另一种状态2,最后使用插入排序完成排序,而状态2的时候使用插入排序比状态1的时候使用插入排序更快。
希尔排序的思想就是使得数组中间隔为h的元素都是有序的(称为h有序数组)。换句话说就是将一个数组划分成h个小数组,而小数组内部是有序的。
算法思想:
我们将需要排序的乱序数组划分成h组
不断缩小h,直到h=1
h=1,即我们学的插入排序,h不断缩小的过程就是我们将数组从状态1转化成为状态2的操作
算法图解:
小数组之间使用排序:
然后将h缩小
小数组之间再进行排序
然后h=1
使用插入排序
希尔排序完成!!!
那么我们最开始使用的h怎么去确定呢?
在《算法 第四版》对于希尔排序的算法代码为:
package sort;
/**
*@time 2020年4月25日:下午7:40:23
*@author Weirdo
*@version 1.0
**/
public class Shell {
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] arr= {5,1,2,4,3};
sort(arr);
for(int i:arr) {
System.out.print(i+" ");
}
}
public static void sort(int[] a){
int N = a.length;
int h=1;
//这里就是对h最大能取多大进行判定
while(h<N/3){
h=3*h+1; // 1,4,13,40
}
while(h>=1){
for(int i=h;i<N;i++){
//less(a[j],a[j-h]) --> a[j]<a[j-h]?true:false
for(int j=i; j>=h && less(a[j],a[j-h]);j-=h){
//交换
exchang(a,j,j-h);
}
}
h=h/3;
}
}
private static boolean less(Integer i, Integer j) {
// TODO Auto-generated method stub
return i.compareTo(j)<0;
}
private static void exchang(int[] a, int j, int i) {
// TODO Auto-generated method stub
int temp=a[j];
a[j]=a[i];
a[i]=temp;
}
}
和上面图解算法不同的地方在于,图解算法的h是成倍减小的,算法里的h是通过while()循环确定最大值,然后减少3倍,算法的思想都是一样的,通过划分数组,数组内部有序,缩小h
快速排序
使用了分治的思想
算法步骤:
1 从数列中挑出一个元素,称为 “基准”(pivot),
2 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
3 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。
算法图解:
一轮排序完毕,3左边的肯定小于3,3右边的肯定大于3
然后左右分别进行快速排序……
Java代码:
public static void quickSort(int[] a , int low,int high) {
int start = low;
int end = high;
int temp = a[start];//标记值
while (start < end){
//通行条件:while()中的值为停止循环条件,即标记值更小就停止循环
while (start < end && temp <= a[end]){
end--;
}
//再次确认标记值是否大于后面的值
if (temp > a[end]){
a[start] = a[end];
a[end] =temp;
}
while (start < end && temp >= a[start]){//标记值更大就停止循环
start++;
}
if (temp < a[start]){
a[end] = a[start];
a[start] = temp;
}
}
//当start >= end 时将跳出循环
//此时temp值一定会在数组的中间 即(a =2,1,3,4,6,5,), 左边比3小,右边比3大
if(start-1 > low ){
quickSort(a,low,start-1);
}
if(end+1<high){
quickSort(a,end+1,high);
}
}
初始化:a = [3, 5, 1, 4, 6, 2]
快排一次结果a--------[2, 1, 3, 4, 6, 5]
递归左
快排二次结果a--------[1, 2, 3, 4, 6, 5]
递归右
快排三次结果a--------[1, 2, 3, 4, 6, 5]
递归右
快排四次结果a--------[1, 2, 3, 4, 5, 6]