二分检索
基本思想
通过 x 与中位数的比较,将原问题归结为规模减半的子问题,如果 x 小于中位数,则子问题由小于 x 的数构成,否则子问题由大于 x 的数构成。
步骤
- 假设表中元素是按升序排列
- 将表中间位置记录的关键字与检索关键字比较,如果两者相等,则检索成功;
- 否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于检索关键字,则进一步检索前一子表,否则进一步检索后一子表。
- 重复以上过程,直到找到满足条件的记录,检索成功;或直到子表不存在为止,此时检索不成功。
优点
- 比较次数少。
- 检索速度快。
- 平均性能好。
缺点
- 待查表为有序表。
- 待查表必须采用顺序存储结构。
- 插入删除困难。
- 只适用于不经常变动而检索频繁的有序列表。
时间复杂度分析
二分检索法充分利用了元素间的次序关系,采用分治策略,可在最坏的情况下用O(log n)完成搜索任务。
代码实现
#include <iostream>
#include <vector>
using namespace std;
int BinSearch(vector<int>&v,int key)
{
int left,right,mid;
left=0;
right=v.size()-1;
while(left<=right)
{
mid=(left+right)/2;
if(key<v[mid]) //key小于中间值时
right=mid-1;//确定左子表范围
if(key>v[mid]) //key 大于中间值时
left=mid+1;//确定右子表范围
if(key==v[mid])//当key等于中间值时,证明查找成功
return mid;
}
if(left > right)
return -1;
}
int main()
{
int key = 5;
vector<int> v = {1,2,3,4,5,6,7,8,9,10};
cout<<BinSearch(v,key)<<endl;
return 0;
}
快速排序
基本思想
任取待排序元素序列中的某元素作为基准值,将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此让整个数据变成有序序列。
将区间按照基准值划分为左右两半部分的常见方式有:
- hoare
- 挖坑法
- 前后指针法
划分左右两半部分
- hoare
int Partition1(vector<int>&v, int left, int right)
{
int begin = left;
int end = right;
int value=v[right];
while (begin < end)
{
while (begin < end && v[begin] <= value)
{
begin++;
}
while (begin < end && v[end] >= value)
{
end--;
}
Swap(v + begin, v + end);
}
Swap(v + begin, v + right);
return begin;
}
- 挖坑法
int Partition2(vector<int>&v, int left, int right)
{
int begin = left;
int end = right;
int pivot = v[right];
while (begin < end)
{
while (begin < end && v[begin] <= pivot)
{
begin++;
}
v[end] = v[begin];
while (begin < end && v[end] >= pivot)
{
end--;
}
v[begin] = v[end];
}
v[begin] = pivot;
return begin;
}
- 前后指针法
int Partition3(vector<int>&v, int left, int right)
{
int d = left;
for (int i = left; i < right; i++)
{
if (v[i] < v[right])
{
Swap(v + i, v + d);
d++;
}
}
Swap(v + d, v + right);
return d;
}
快速排序
void QuickSort(vector<int>&v, int left, int right)
{
if (left >= right)
{
return;
}
int div;
div = Partition1(v, left, right);
QuickSort(v, left, div - 1);
QuickSort(v, div + 1, right);
}
用栈实现快速排序
#include <stack>
void QuickSortNor(vector<int>&v, int size)
{
std::stack<int> stack;
stack.push(size - 1); // right
stack.push(0); // left
while (!stack.empty())
{
int left = stack.top();
stack.pop();
int right = stack.top();
stack.pop();
if (left >= right)
{
continue;
}
else
{
int d = Partition1(v, left, right);
// [d + 1, right]
stack.push(right);
stack.push(d + 1);
// [left, d - 1]
stack.push(d - 1);
stack.push(left);
}
}
}
效率分析
时间
- 最好情况下,每次划分所选择的中间数恰好将当前序列几乎等分,时间复杂度为O(nlogn)。
- 最坏情况下,每次所选的中间数是当前序列中的最大或最小元素,时间复杂度为O(n2)。
- 平均时间复杂度是O(nlogn)。
空间
- 从空间性能上看,快速排序只需要一个元素的辅助空间,但快速排序需要一个栈空间来实现递归。
- 最好情况下,所需栈的最大深度为log2(n+1)。
- 最坏情况下,所需栈的最大深度为n。
- 空间复杂度为O(logn))。
稳定性
- 快速排序是一种不稳定的排序算法。
归并排序
基本思想
- 划分
将原问题归结为规模为n/2 的2 个子问题,继续划分,将原问题归结为规模为n/4 的4 个子问题. 继续…,当子问题规模为1 时,划分结束。 - 归并
从规模1到n/2,陆续归并被排好序的两个子数组。每归并一次,数组规模扩大一倍,直到原始数组。即先使每个子序列有序,再使子序列段间有序。
代码实现:
void Merge(int array[], int left, int mid, int right, int extra[])
{
int size = right - left;
int left_index = left;
int right_index = mid;
int extra_index = 0;
while (left_index < mid && right_index < right)
{
if (array[left_index] <= array[right_index])
{
extra[extra_index] = array[left_index];
left_index++;
}
else
{
extra[extra_index] = array[right_index];
right_index++;
}
extra_index++;
}
while (left_index < mid)
{
extra[extra_index++] = array[left_index++];
}
while (right_index < right)
{
extra[extra_index++] = array[right_index++];
}
for (int i = 0; i < size; i++)
{
array[left + i] = extra[i];
}
}
// 要排序的区间是 array[left, right)
void __MergeSort(int array[], int left, int right, int extra[])
{
if ((right == left + 1) || (right <= left))
{
return;
}
int mid = left + (right - left) / 2;
// [left, mid) [mid, right)
__MergeSort(array, left, mid, extra);
__MergeSort(array, mid, right, extra);
Merge(array, left, mid, right, extra);
}
void MergeSort(int array[], int size)
{
int *extra = (int *)malloc(sizeof(int)* size);
__MergeSort(array, 0, size, extra);
free(extra);
}
void MergeSortNor(int array[], int size)
{
int *extra = (int *)malloc(sizeof(int)* size);
for (int i = 1; i < size; i = i * 2)
{
for (int j = 0; j < size; j = j + 2 * i)
{
int left, mid, right;
left = j;
mid = j + i;
right = mid + i;
if (mid >= size)
{
continue;
}
if (right > size)
{
right = size;
}
Merge(array, left, mid, right, extra);
}
}
free(extra);
}
特性总结:
- 缺点在于需要O(N)的空间复杂度,更多的是解决在磁盘中的外排序问题。
- 时间复杂度:O(N*logN)。
- 空间复杂度:O(N)。
- 稳定性:稳定。