一、冒泡排序
1.算法思想
冒泡排序(Bubble Sort)是一种简单的排序算法。
为什么会叫做冒泡排序呢?这是由于它的算法思想就类似于鱼儿在河里吐泡泡的场景,例如升序排列一列数,它会两两相邻的数据进行比较,如果前者大于后者就交换,重复此番工作直到交换到最后两个数据,第一趟冒泡排序已经完成,最大的数据被冒到数组的最后一个位置,继而缩小冒泡的区间,又从头开始第二趟冒泡,直到次大数被放在倒数第二个位置,以此类推,直到所有数据被冒到合适位置,冒泡排序就算完成。
2.算法的具体步骤
(1)比较相邻的元素。如果第一个比第二个大,就交换他们两个。
(2)对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
(3)针对所有的元素重复以上的步骤,除了最后一个。
(4)持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
3.图解举例
4.代码实现
//冒泡排序
void BubbleSort(int* arr, int size)
{
if (arr == NULL || size <= 0)
return;
for (int i = 0; i < size; i++)
{
for (int j = 0; j < size - i - 1; j++)
{
if (arr[j]>arr[j + 1])
{
//swap(arr[j], arr[j + 1]);
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
优化版本
void BubbleSort(int* arr, int size)
{
int flag = 0;
for (int i = 0; i < size; i++)
{
flag = 0;
for (int j = 0; j < size - 1 - i; j++)
{
if (arr[j]>arr[j + 1])
{
swap(arr[j], arr[j + 1]);
flag = 1;
}
}
}
if (flag == 0)
{
return;
}
}
5.其他
(1)时间复杂度:O(n^2)
(2)空间复杂度:O(1)
(3)稳定性:稳定
6.动态图
二、快速排序
1.算法思想
本质上,快速排序就是冒泡排序的一种改进,冒泡排序是通过每一趟冒泡将最大值(最小值)放到恰当位置。
而快速排序则是每趟排序从待排序区间选一个基准值,将比它小的数据全放在其左边,将比它大的值放在其右边然后递归其左右子区间对其排序,一层层递归下去,某区直到间只剩一个数据时,停止递归,此子区间已经算是有序,继而向其上层区间返回,一层层向上返回,当首次基准值的左右区间均已有序时,整个排序就算完成。
2.算法具体的步骤
(1)从数列中挑出一个元素,称为 “基准”。
(2)重新排序数列,所有元素比基准值小的摆放在基准左边,所有元素比基准值大的摆在基准的右边(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置,这个称为分区操作。
(3)递归地把小于基准值元素的子数列和大于基准值元素的子数列排序。
3.算法种类
目前比较热门的有三种实现快排的算法:左右指针法、挖坑法、前后指针法。
4.不同算法实现及其优化
(1)左右指针法
1> 算法的思想步骤
- 用两个指针left和right用来标识区间范围,这里我将key值定义为区间最右边的值,所以要左指针开始走(当然,你也可以将key值定义为区间最左边的值)。
- 左指针向右找比key值大的数据,找到后停下来,现在右指针向左开始找比key小的数据,找到后将左右指针的值交换。
- 左指针继续找比key大的值,右指针继续找比key小的值,找到后交换,重复此过程,直到左右指针相遇,然后将左指针所在位置的值赋值为key。
- 此时比key值小的数据全部在key的左边,比key大的值全在key的右边。
- 按照上述同样的方法递归以上key值的左右区间,使之有序后排序完成。
2> 算法思想如下图
3> 代码实现
//左右指针法
int quicksort1(int* arr, int left, int right)
{
if(arr==NULL||left>right)
return NULL;
int key = right;
while (left < right)
{
while (arr[left] < arr[key])
{
++left;
}
while (arr[right]>arr[key])
{
--right;
}
if (arr[left] != arr[right])
{
//swap(arr[left],arr[right]);
int tmp = arr[left];
arr[left] = arr[right];
arr[right] = tmp;
}
}
swap(arr[left], arr[right]);
return left;
}
(2)挖坑法
1> 算法的思想步骤
用两个指针left和right用来标识区间范围,初始坑设置到key值的地方。我们将key值定义为区间最右边的值。
左指针向右找比key值大的数据,找到后将左指针所指的数据填入坑中,将坑更新为左指针所指位置。现在右指针向左开始找比key小的数据,找到后将右指针所指的数据填入坑中,将坑更新为右指针所指位置。
重复上述过程,左右指针继续走,向中间靠拢直到左右指针相遇,然后将坑处的值值赋值为key。
此时比key值小的数据全部在key的左边,比key大的值全在key的右边。
按照上述同样的方法递归以上key值的左右区间,使之有序后排序完成。
2> 思想展示图
3> 代码实现
//挖坑法
int quicksort2(int* arr, int left, int right)
{
if(arr==NULL||left>right)
return NULL;
int key = right;
int blank = right;
while (left < right)
{
while (arr[left] <= arr[key])
{
left++;
}
arr[blank] = arr[left];
blank = left;
while (arr[right]>=arr[key])
{
right--;
}
arr[blank] = arr[right];
blank = right;
}
arr[blank] = key;
return blank;
}
(3)前后指针法
1> 算法具体步骤
- 定义两个指针prev和cur,最开始cur指向最左边的位置,prev指向cur的前面(即就是空)。
- 定义key值为最右边的数字。
- 当cur小于key时,cur和prev同时++,大于等于时cur++,prev不变。如果cur一直找不到比key小的值,cur走到最右边,此时prev++,并且交换prev和cur的值。
- 重复上述过程,当cur和prev相等时,排序结束。
2> 算法的具体图形
3> 代码实现
//前后指针法
int quicksort3(int* arr, int left, int right)
{
if (arr == NULL || left > right)
return NULL;
int prev = left-1;
int cur = left;
int key = arr[right];
while (cur != right)
{
if (arr[cur] < key&&arr[cur] != arr[prev])
{
//swap(arr[cur], key);
int tmp = arr[cur];
arr[cur] = key;
key = tmp;
}
++cur;
}
//swap(arr[++prev], arr[cur]);
int tmp = arr[prev];
arr[prev] = arr[cur];
arr[cur] = tmp;
return prev;
}
5.快排优化
(1)三数取中法
1> 基本思想:
- 为了把区间尽可能的分配均匀,在取基准值的时候,选取数组最左边,最中间,最右边的元素三个元素中中间大小的元素作为基准值。
2> 代码实现:
//三数取中法
int GetMidIndex(int* arr, int left, int right)
{
int mid = left + ((right - left) >> 1);
if (arr[left] < arr[mid])
{
if (arr[mid] < arr[right])
{
return mid;
}
else if (arr[left]<arr[right])
{
return right;
}
else
{
return left;
}
}
else
{
if (arr[mid] > arr[right])
{
return mid;
}
else if (arr[left] > arr[right])
{
return right;
}
else
{
return left;
}
}
}
(2)
1> 算法思想
- 当区间较小时候,一般认为有13个元素以下,这个时候,快速排序就没有直接插入的性能好了,因为当区间比较小的时候,区间划分的就比较多,快排就像一颗二叉树一样,每一次递归都相当于增加一层高度,当区间比较小的时候,就会快速增加二叉树高度,降低了效率,因此我们在划分到小区间的时候就改为插入排序。
2> 代码实现
//小区间优化
void InsertSort(int* arr, int n)
{
if (arr == NULL || n <= 0)
return;
int end = 0;
for (int i = 1; i < n; i++)
{
int tmp = arr[i];
end = i - 1;
while (end >= 0)
{
if (arr[end]>tmp)
{
arr[i + 1] = arr[end];
--end;
}
else
{
break;
}
}
arr[i + 1] = tmp;
}
}
(3)非递归实现快排
1> 算法思想
- 快排是基于递归子区间来实现的,而当数据元素比较多的时候,难免递归的次数会非常多,而每次函数的递归都是一个函数的栈帧过程,十分消耗时间,因此可以借助栈这种方式,来实现递归转非递归。
2> 代码实现
#include<stack>
//非递归
void QuickSortNoeR(int* arr, int left, int right)
{
stack<int> s;
s.push(left);
s.push(right);
while (s != empty())
{
int start = s.top();
s.pop();
int finish = s.top();
s.pop();
int div = quickSort1(array, start, finish);
if (start < div - 1)
{
s.push(div - 1);
s.push(start);
}
if (finish > div + 1)
{
s.push(finish);
s.push(div + 1);
}
}
}
6.其他
(1)时间复杂度:O(NlogN)
快速排序的最好情况:
快速排序的最好情况是每次都划分后左右子序列的大小都相等,其运行的时间就为O(N*1ogN)。
快速排序的最坏情况:
快速排序的最坏的情况就是当分组重复生成一个空序列的时候,这时候其运行时间就变为O(N*N)
(2)空间复杂度:O(logN)
(3)稳定性:不稳定