文章目录
一、冒泡排序
1.1 基础的冒泡排序
算法思想:从后向前,相邻两个元素进行两两比较,如果后面的元素的值小于前面元素的值,就交换两个元素。一趟冒泡下来,最小的元素就会被放到最前面的位置。这样最多进行n-1趟冒泡,就会把所有的元素全部排好。
相关代码如下:
void Bubble_sort(int[] array){
for(int i=0; i<array.length; i++){
for(int j=array.length-1; j>i; j--){
//一趟冒泡排序:从后往前,相邻两个元素进行两两比较
if(array[j]<array[j-1]){
//如果后面的元素小于前面的元素,就交换
Swap(array, j, j-1);
}
}
}
}
/*
* 时间复杂度:
* 最坏情况:O(n^2) 最好情况(有序时):O(n)
* 空间复杂度:O(1)
* 稳定性:稳定
*/
1.2 冒泡排序的优化(增加flag标志)
算法思想:在基础冒泡排序的算法上,增加flag标志,用来标记当前冒泡过程中是否发生元素交换。如果在某一趟的比较过程中,发现没有任何元素移动,就不再进行接下来的比较。
相关代码如下:
void Bubble_sort(int[] array){
for(int i=0; i<array.length; i++){
boolean flag = false; //设置一个是否发生元素的标志
for(int j=array.length-1; j>i; j--){
if(array[j]<array[j-1]){
Swap(array, j, j-1);
flag = true;
}
}
if(flag==false)
//该趟没有发生元素交换,表明该数组已经有序,直接返回
return;
}
}
1.3 冒泡排序的优化(双向冒泡排序法)
算法思想:在待排序列的正反两个方向交替进行扫描,即,第一趟把元素值最大的放在序列的最后面,第二趟把元素值最小的放在序列的最前面,如此反复进行,直到整个待排序列有序。
基本步骤:
1.在基础冒泡排序的算法上进行操作
2.奇数趟时,从前向后比较相邻元素的值,如果前面元素的值大于后面元素的值就交换,直到把序列中最大值元素移动到序列的最右端
3.更新上界
4.偶数趟时,从后向前比较相邻元素的值,如果后面元素的值小于前面元素的值就交换,直到把序列中最小值元素移动到序列的最右端
5.更新下界
6.直到整个序列有序
相关代码如下:
void Bubble_sort(int[] array, int left, int right) {
int i = 0;
boolean flag = true; // 用来标记一趟冒泡排序是否发生,初始为true
while (left < right && flag == true) {
flag = false;
// 第一趟先把元素值最大的放在序列的最后面
for (i = left; i < right; i++) {
if (array[i] > array[i + 1]) { // 前一个元素的值大于后一个元素的值
Swap(array, i, i + 1);
flag = true; // 发生了元素交换,flag置为true
}
}
right--; // 更新上界
// 第二趟把元素值最小的放在序列的最前面
for (i = right; i > left; i--) {
if (array[i] < array[i - 1]) { // 后一个元素的值小于前一个元素的值
Swap(array, i, i - 1);
flag = true; // 发生了元素交换,flag置为true
}
}
left++; // 更新下界
}
}
二、快速排序
2.1 基础的快速排序
算法思想:先在待排序列中任取一个元素作为pivot基准值,通过一趟排序把待排序列分成两个部分:前一部分元素都小于基准值,后一部分元素都大于基准值。此时,pivot就被放到了最终位置上。然后,分别对两个字表重复上述步骤,直到每一个部分只有一个元素或者为空。
一趟快速排序实际上是一个交替搜索与排序的过程。
快排的时间效率与基准值划分的字表是否对称有关:当待排序列被划分为两个等长的子表时,排序的速度最快。
当初始表基本有序或者基本逆序时,快排的时间效率最低(为一棵单支二叉树)
2.1.1 双指针法
基本步骤:
1.把第一个元素取下来,作为pivot
2.一个指针从尾开始找比pivot小的数
3.另一个指针从头开始找比pivot大的数
4.如果找到,就交换两个数
5.最后,把pivot和尾指针指向的数交换(此时,pivot被放到其最终位置)
相关代码如下:
//基于分治的思想
int Partition(int[] array, int left, int right){
int pivot = array[left];
while(left<right){
while(left<right && array[right]>=pivot){
right--;
}
//找到了比pivot小的数,交换,放到前面
Swap(array, right, left);
while(left<right && array[left]<pivot){
left++;
}
//找到了比pivot大的数,交换,放到后面
Swap(array, left, right);
}
//找到了pivot的最终位置
array[left] = pivot;
return left;
}
void Qucik_sort(int[] array, int left, int right){
if(left<right){
//先找基准值
int partition = Partition(array, left, right);
//以基准值为界,依次对左右两个字表进行递归排序
Qucik_sort(array, left, partition-1);
Qucik_sort(array, partition+1, right);
}
}
/*
* 快速排序时间复杂度:
* 最坏情况:O(n^2) 最好情况(有序时):O(nlogn)
* 空间复杂度:最大递归深度为n,最小递归深度为logn
* 最坏情况:O(n) 最好情况(有序时):O(nlogn)
* 稳定性:不稳定
*/
2.1.2 挖坑法
基本步骤:
1.把第一个元素的值pivot拿出来,把第一个位置当做坑
2.一个指针从尾开始找比pivot小的数,找到以后,放在坑里,该指针所在的位置作为新的坑
3.另一个指针从头开始找比pivot大的数,找到以后,放在坑里,该指针所在的位置作为新的坑
4.直到头尾指针相遇
5.然后把第一个数据放在最后一个坑里
相关代码如下:
int Partition1(int[] array, int left, int right) {
int pivot = array[left]; // 取第一个元素作为坑
while (left < right) {
//从尾开始找比pivot小的数,找到以后,放在坑里,该指针所在的位置作为新的坑
while (array[right] >= pivot && left < right) {
right--;
}
array[left] = array[right];
//从头开始找比pivot大的数,找到以后,放在坑里,该指针所在的位置作为新的坑
while (array[left] < pivot && left < right) {
left++;
}
array[right] = array[left];
}
// 把第一个数据放在最后一个坑里
array[left] = pivot;
return left;
}
2.1.3 前后指针法
基本步骤:
1.取第一个元素的值pivot,让prev指向第一个数据,让cur指向第二个数据
2.让cur从第二个数据开始依次遍历每一个数据,如果cur所指向的数据比pivot小,就把它和prev++所指向的数据交换(要保证prev++ != cur)
3.最后,让prev所指向的数据和pivot交换
相关代码如下:
int Partition2(int[] array, int left, int right) {
// 取第一个元素为pivot,让prev指向第一个数据,让cur指向第二个数据
int pivot = array[left];
int prev = left;
int cur = left + 1;
// 让cur依次遍历每一个元素
while (cur <= right) {
if (array[cur] <= pivot && prev++ != cur) {
Swap(array, cur, prev);
}
++cur;
}
Swap(array, prev, left);
return prev;
}
2.2 快排的优化(在一定范围内使用直接插入排序)
void Qucik_sort2(int[] array, int left, int right){
if(left >= right)
return;
//优化方式一:在right-left+1<=100范围里,使用直接插入排序
if(right-left+1 <= 100){
Insert_sort(array, left, right);
}
if(left < right){
int partition = Partition_1(array, left, right);
Qucik_sort2(array, left, partition-1);
Qucik_sort2(array, partition+1, right);
}
}
2.3 快排的优化(使用三数取中法确定基准值)
基本思想:利用三数取中法,把中间的元素换到最左边(相当于传统划分方法中的第一个位置上的元素),一般是在基本有序的范围内使用。
即,实现array[mid]<=array[left]<=array[right]
的效果。
相关代码如下:
void three_num_mid(int[] array, int left, int right){
int mid = (left+right)/2;
//mid下标的为中间元素,使其放到第一个位置,作为基准值
if(array[mid] > array[left]){
Swap(array, mid, left);
}
if(array[mid] > array[right]){
Swap(array, mid, right);
}
if(array[left] > array[right]){
Swap(array, left, right);
}
}
void Quick_sort3(int[] array, int left, int right) {
if (left >= right)
return;
if (right - left + 1 <= 100) {
Insert_sort(array, left, right);
}
three_num_mid(array, left, right);
int partition = Partition_1(array, left, right);
Quick_sort3(array, left, partition - 1);
Quick_sort3(array, partition + 1, right);
}