一、交换排序
利用交换元素的位置进行排序的方法称作交换排序。常用的交换排序的方法有冒泡排序和快速排 序。快速排序是一种分区交换排序方法。
二、冒泡排序
之前已经介绍过冒泡排序,点击打开链接
冒泡排序最好情况时间复杂度O(n),冒泡排序最坏情况下时间复杂度O(n^2). 冒泡排序空间复杂度O(1),冒泡排序是一种稳定的排序算法。
三、快速排序
快速排序,实际上是找一个基准值,将这个数组分成两个部分,左边部分的数据都比右边部分的数据要小,再按照此方法对子区间进行划分进行排序。
算法思想是:
1、开始时设置两个变量left,right,给定一个基准值key。
2、left向后移,找到第一个比key值大的数,否则继续向后走,。
3、right向前移,找到第一个比key小的数,否则继续向前走。
4、判断是否满足条件left小于right,不满足则交换,否则重复步骤2和步骤3,直到left和right相遇,这样所有的数就有序。
递归:
void QuickSort(int *array, int left, int right)
{
if (left < right)
{
size_t div = Pation1(array, left, right);
//size_t div = Pation2(array, left, right);
//size_t div = Pation3(array, left, right);
QuickSort(array, left, div);
QuickSort(array, div + 1, right);
}
}
方法一:左右指针法
代码如下:
int Pation1(int *array, int left, int right)
{
size_t begin = left;
size_t end = right - 1;
int key = array[end];
while (begin < end)
{
while (begin < end&&array[begin] <= key)
begin++;
while (begin < end&&array[end] >= key)
end--;
if (begin < end) //没有相遇
swap(array[begin], array[end]);
}
if (begin!=right)
swap(array[begin], array[right-1]);
return begin;
}
方法二:挖坑法
先将最左边或者最右边取为基准值,保留这个值,这个位置就是起始坑,然后从左边开始遍历,找到第一个比坑里面的值大的数就交换,相当于用大的数填坑,以前的位置就会形成新的坑。然后我们可在右边找比坑的值小的数入坑, 又会形成新的坑,这样不断遍历走子问题直到两个坑相遇.
第一种类似只是保留了key的值,换成坑,然后不断找新的值去填坑,直到相遇。
//挖坑法
int Pation2(int *array, int left, int right)
{
size_t begin = left;
size_t end = right - 1;
int key = array[end];
while (begin < end)
{
while (begin < end&&array[begin] <= key)
begin++;
if (begin < end)
array[end--] = array[begin];
while (begin < end&&array[end] >= key)
end--;
if (begin < end)
array[begin++] = array[end];
}
array[begin] = key;
return begin;
}
方法三:
前两种都是从两边遍历,第三种是从一边取两个指针。如下:
取两个指针prev和cur,如下图:
代码如下:
int Pation3(int *array, int left, int right)
{
int key = array[right - 1];
//int key = GetMidDataIdx(array, left, right);
int cur = 0;
int pre = cur - 1;
while (cur < right)
{
//不相邻
if (array[cur] < key&&pre++ != cur) //前者不成立后者不执行
swap(array[pre], array[cur]);
cur++;
}
if (++pre != right)
swap(array[pre], array[right - 1]);
return pre;
}
以上三种方法的基准值都是取最左或最右,若取的值是最小或者最大的值,效率就很低。我们可以用三数取中法。
它并不是选择待排数组的第一个数作为中轴,而是选用待排数组最左边、最右边和最中间的三个元素的中间值作为中轴。
//三数取中
int GetMidDataIdx(int *array, int left, int right)
{
int mid = left + ((right - left) >> 1);
if (array[left] < array[right])
{
if (array[left]>array[mid])
return left;
else if (array[right] < array[mid])
return right;
else
return mid;
}
else
{
if (array[right]>array[mid])
return right;
else if (array[left] < array[mid])
return left;
else
return mid;
}
}
非递归
void QuickSortNor(int *array, int size)
{
int left = 0;
int right = size; //左闭右开
stack<int> s;
s.push(right);
s.push(left);
while (!s.empty())
{
left = s.top();
s.pop();
right = s.top();
s.pop();
if (left < right)
{
int div = Pation2(array, left, right);
//保存右半部分区间
s.push(right);
s.push(div + 1);
//保存左半部分区间
s.push(div);
s.push(left);
}
}
}
四、归并排序
基本思想:将待排序的元素序列分成两个长度相等的子序列,为每一个子序列排序,然后将他们合并成一个序列。合并两个子序列的过程称为两路归并。
第一步:划分,为每一个子序列排序
第二步:归并
void Merge(int *array, int left,int mid, int right, int *temp)
{
int index1 = left;
int index2 = mid;
int index = left;
while (index1 < mid&&index2 < right)
{
if (array[index1] < array[index2])
temp[index++] = array[index1++];
else
temp[index++] = array[index2++];
}
//如果有剩余元素
while (index1 < mid)
temp[index++] = array[index1++];
while (index2 < right)
temp[index++] = array[index2++];
memcpy(array + left, temp + left, (right - left)*sizeof(int));
}
void _MergeSort(int *array, int left,int right,int *temp)
{
if (left + 1 < right)
{
int mid = left + ((right - left) >> 1);
_MergeSort(array, left, mid,temp);
_MergeSort(array, mid, right, temp);
Merge(array, left, mid, right,temp);
}
}
void MergeSort(int *array, int size)
{
int *temp = new int[size];
_MergeSort(array, 0, size, temp);
delete[] temp;
}
非递归:
//非递归
void MergeSortNor(int *array, int size)
{
int gap = 1;
int *temp = new int[size];
while (gap < size)
{
for (int i = 0; i < size; i += 2 * gap)
{
int left = i;
int mid = i + gap;
int right = mid + gap;
if (mid>size)
mid = size;
if (right>size)
right = size;
Merge(array, left, mid, right, temp);
}
gap *= 2;
}
delete[] temp;
}