交换排序
冒泡
思路:
1、外层循环是遍历每个元素,每次都放置好一个元素。
2、内层循环将剩余序列中所有元素两两比较,将较大的放在后面。
3、重复第二步,直到只剩下一个数。
如果有n个元素,则第一次循环进行n-1次比较,第二次循环进行n-2次,…………,第n-j次循环过后,会按大小顺序排出j个较大的数。
写成代码:
1、设置循环次数。
2、设置开始比较的位数,和结束的位数。
3、两两比较,将最小的放到前面去。
4、重复2、3步,直到循环次数完毕。
/**
* 最优的情况也就是开始就已经排序好序了,这是循环n-1次每次没有交换动作而退出,则时间花销为:n(n-1)/2;所以最优的情况时间复杂度为:O( n^2 );
* 最坏是完全逆序(其中比上面最优的情况所花的时间就是在于交换元素的三个步骤),即需要加上交换移动次数
* 3n(n-1)/2次,所以时间复杂度也是O(N^2)。
*
* 该算法中空间复杂度一般是看交换元素时所使用的辅助空间,即交换元素时那个临时变量所占的内存空间。
* 最优的空间复杂度就是开始元素顺序已经排好了,则空间复杂度为:0;
* 最差的空间复杂度就是开始元素逆序排序了,则空间复杂度为:O(n);
* 平均的空间复杂度为:O(1);
*
* PS:想想使最优的时间复杂度为:O(n)的写法?
* https://blog.csdn.net/yuzhihui_no1/article/details/44339711
*/
@Test
public void bubble(){
int[] a = {3,5,12,4,1,4,1,8};
for(int i=0; i<a.length-1; i++){
for(int j=0; j<a.length-1-i; j++){
if(a[j] > a[j+1]){
ArrUtil.swap(a, j+1, j);
}
System.out.println(Arrays.toString(a));
}
}
}
快速排序
思路:
1、选择第一个数为p,称为 "基准"(pivot);
2、指针i一步一步从左往右寻找大于基数p的数字,指针j从右往左寻找小于基数p的数字,i、j都找到后将两个位置的数字进行交换,然后继续前进比较、交换,直到i>=j结束循环,然后将基准数 p和i(或j)位置的数进行交换,即完成一趟快排,这个称为分区(partition)操作;
此时数组被划分为小于基数p、基数p、大于基数p的三部分;
3、递归地(recursive)把小于基数p的子数列和大于基数p的子数列按上面的步骤排序,直到不能再递归。
代码实现的写法也有好多种
代码1
来自维基的,分割写法有点不一样,访问要梯子
选择排序
简单选择排序
常用于取序列中最大最小的几个数时。(如果每次比较都交换,那么就是冒泡;如果每次比较完一个循环再交换,就是简单选择排序。)
1、遍历整个序列,每次将选出来的最小的数放在前面。
2、遍历剩下的序列,依次与哨兵进行比较,选出最小的数。
3、重复第二步,直到只剩下一个数。
写成代码:
1、首先确定循环次数,并且记住当前数字和当前位置。
2、将当前位置后面所有的数与当前数字进行对比,小数赋值给key并记住小数的位置。
3、比对完成后,将最小的值与第一个数的值交换。
4、重复2、3步。
/**
* 选择排序的时间复杂度:简单选择排序的比较次数与序列的初始排序无关。
* 假设待排序的序列有 N 个元素,则比较次数永远都是N (N - 1)/2。
* 而移动次数与序列的初始排序有关。当序列正序时,移动次数最少,为 0,最多会发生 N - 1次交换移动。
* 所以,综上,简单排序的时间复杂度为 O(N^2)。
*
* 空间复杂度为:O(1);与冒泡同理。
*
* 虽然选择排序和冒泡排序的时间复杂度一样,但实际上,选择排序进行的交换操作很少。
*
* 缺点是不稳定:相同的两个数,位置可能会变。
*/
@Test
public void selectSorted() {
int[] a = { 3, 5, 1, 6, 8, 2, 5 };
for (int i = 0; i < a.length - 1; i++) {
int min = i;
for (int j = i + 1; j < a.length; j++) {
if (a[min] > a[j]) {
min = j;
}
}
if (min != i) {
ArrUtil.swap(a, i, min);
}
}
System.out.println(Arrays.toString(a));
}
堆排序
对简单选择排序的优化。
1、将序列构建成大顶堆。
2、将根节点与最后一个节点交换,然后断开最后一个节点。
3、重复第一、二步,直到所有节点断开。
/**
* 堆排序是指利用堆积树(堆)这种数据结构所设计的一种排序算法。一般升序采用大顶堆,降序采用小顶堆
* 把最大堆堆顶的最大数取出,将剩余的堆继续调整为最大堆,再次将堆顶的最大数取出,这个过程持续到剩余数只有一个时结束。
*
*
* 缺点是不稳定:相同的两个数,位置可能会变。
*/
@Test
public void heapSort() {
int[] a = { 4,9,7,6,5,4,2,8,6,7,9,10,12};
// 循环建堆
for (int i = 0; i < a.length - 1; i++) {
// 建堆
// buildMaxHeap(a, a.length - 1 - i);
buildMaxHeap1(a, a.length - 1 - i);
// 交换堆顶和最后一个元素
ArrUtil.swap(a, 0, a.length - 1 - i);
System.out.println(Arrays.toString(a));
}
}
//从第一个非叶子结点从下至上,从右至左建大顶堆
private void buildMaxHeap1(int[] data, int lastIndex) {
// TODO Auto-generated method stub
//从lastIndex处节点(最后一个节点)的父节点开始
for(int i=(lastIndex-1)/2;i>=0;i--){
//k保存正在判断的节点
int k=i;
//如果当前k节点的子节点存在
while(k*2+1<=lastIndex){
//k节点的左子节点的索引
int biggerIndex=2*k+1;
//如果biggerIndex小于lastIndex,即biggerIndex+1代表的k节点的右子节点存在
if(biggerIndex<lastIndex){
//若果右子节点的值较大
if(data[biggerIndex]<data[biggerIndex+1]){
//biggerIndex总是记录较大子节点的索引
biggerIndex++;
}
}
//如果k节点的值小于其较大的子节点的值
if(data[k]<data[biggerIndex]){
//交换他们
ArrUtil.swap(data,k,biggerIndex);
//将biggerIndex赋予k,开始while循环的下一次循环,重新保证k节点的值大于其左右子节点的值
k=biggerIndex;
}else{
break;
}
}
}
}
插入排序
直接插入排序
把新的数据插入到已经排好的数据列中。
1、将第一个数和第二个数排序,然后构成一个有序序列
2、将第三个数插入进去,构成一个新的有序序列。
3、对第四个数、第五个数……直到最后一个数,重复第二步。
写成代码:
1、首先设定插入次数,即循环次数,for(int i=1;i<length;i++),1个数的那次不用插入。
2、设定插入数和得到已经排好序列的最后一个数的位数。insertNum和j=i-1。
3、从最后一个数开始向前循环,如果插入数小于当前数,就将当前数向后移动一位。
4、将当前数放置到空着的位置,即j+1。
希尔排序
对于直接插入排序问题,数据量巨大时。
1、将数的个数设为n,取奇数k=n/2,将下标差值为k的书分为一组,构成有序序列。
2、再取k=k/2 ,将下标差值为k的书分为一组,构成有序序列。
3、重复第二步,直到k=1执行简单插入排序。
写成代码:
1、首先确定分的组数。
2、然后对组中元素进行插入排序。
3、然后将length/2,重复1,2步,直到length=0为止。
归并排序
速度仅次于快排,内存少的时候使用,可以进行并行计算的时候使用。
1、选择相邻两个数组成一个有序序列。
2、选择相邻的两个有序序列组成一个有序序列。
3、重复第二步,直到全部组成一个有序序列。
分配排序
基数排序
用于大量数,很长的数进行排序时。
1、将所有的数的个位数取出,按照个位数进行排序,构成一个序列。
2、将新构成的所有的数的十位数取出,按照十位数进行排序,构成一个序列。