分治算法应用举例_二分检索、快速排序、归并排序

二分检索

基本思想

通过 x 与中位数的比较,将原问题归结为规模减半的子问题,如果 x 小于中位数,则子问题由小于 x 的数构成,否则子问题由大于 x 的数构成。

步骤

  • 假设表中元素是按升序排列
  • 将表中间位置记录的关键字与检索关键字比较,如果两者相等,则检索成功;
  • 否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于检索关键字,则进一步检索前一子表,否则进一步检索后一子表。
  • 重复以上过程,直到找到满足条件的记录,检索成功;或直到子表不存在为止,此时检索不成功。

优点

  • 比较次数少。
  • 检索速度快。
  • 平均性能好。

缺点

  • 待查表为有序表。
  • 待查表必须采用顺序存储结构。
  • 插入删除困难。
  • 只适用于不经常变动而检索频繁的有序列表。

时间复杂度分析

二分检索法充分利用了元素间的次序关系,采用分治策略,可在最坏的情况下用O(log n)完成搜索任务。

代码实现

#include 
#include 
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;
}

快速排序

基本思想
任取待排序元素序列中的某元素作为基准值,将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此让整个数据变成有序序列。

将区间按照基准值划分为左右两半部分的常见方式有:

  1. hoare
  2. 挖坑法
  3. 前后指针法

划分左右两半部分

  • 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    
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);
}

特性总结:

  1. 缺点在于需要O(N)的空间复杂度,更多的是解决在磁盘中的外排序问题。
  2. 时间复杂度:O(N*logN)。
  3. 空间复杂度:O(N)。
  4. 稳定性:稳定。

你可能感兴趣的:(算法,分治算法,快速排序,归并排序,二分检索)