欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

冒泡排序_快速排序(划分)_1求数组中第k大的数_2求数组中出现次数超过一半的数_3求数组中最小的k个数

程序员文章站 2022-04-25 15:17:04
...

目录:

0.对函数参数合理性的检查(边界条件)会抛出异常(C++):throw new std::exception("Invalid Parameters");

STL中的检测和报告错误:标准异常和断言

<stdexcept>  <cassert>

<stdexcept>:

namespace std{
class logic_error;    //逻辑错误
class domian_error;    //域错误
class invalid_error;    //无效参数
class lenght_error;    //长度错误
class out_of_range;    //超出范围
class runtime_error;    //运行时错误
class range_error;    //范围错误
class overflow_error;    //上溢出错误
class underflow_error;    //下溢出错误
}

 

1.几种排序算法比较

2.冒泡排序

3.快速排序

3.1划分函数的实现1

3.2划分函数的实现2(STL partiton #include <algorithm> std::partition)

4.求数组中第k大的数

5.已序顺序表二分查找循环实现

6.已序顺序表二分查找递归实现

7.求数组中出现次数超过一半的数

8.求数组中最小的k个数(法1:基于快排划分思路;法2:基于大顶堆容器,流式(循环读入)处理海量数据的思路)

 

 

3.2 C++ STL algorithm库提供的与集合元素划分相关的函数:

函数 功能
partition 将元素划分为两个集合
is_partitioned 测试集合是否被划分
stable_partition 将元素划分为两个集合,保持元素的相对顺序
partition_copy 将元素划分为两个集合
partition_point 获取划分点

 

1.几种排序算法比较

排序方法

平均时间

最坏情况

辅助空间

稳定性

冒泡排序

O(n2)

O(n2)

O(1)

稳定

快速排序

O(nlogn)

O(n2)

O(logn)~O(n)

不稳定

选择排序

O(n2)

O(n2)

O(1)

稳定

堆排序

O(nlogn)

O(nlogn)

O(1)

不稳定

归并排序

O(nlogn)

O(nlogn)

O(n)

稳定

...

...

...

...

...

 

 2.冒泡排序

template<typename T>
void bubbleSort(vector<T>& vec)
{
	int i, j;
	T temp;
	int n= vec.size();
	for (i = 0; i < n- 1; i++)
	{
		for (j = 0; j < n- i - 1; j++)
		{
			if (vec[j]>vec[j + 1])
			{
				temp = vec[j];
				vec[j] = vec[j + 1];
				vec[j + 1] = temp;
			}
		}
	}
}

3.快速排序

3.1划分函数实现1(参考《数据结构C语言版本严》)

3.2直接使用C++ STL 的函数std::partition

/*对顺序表L的快速排序:*/

面试的时候,如果要直接写快排,就用严奶奶书上的这个版本!


/*对顺序表L的快速排序:*/
//一次划分,将枢轴记录放到最终有序表的正确的位置上!
int Partition(vector<int> &L, int low, int high)
{
	int pivotkey = L[low];//设立枢轴记录,这里是这区间的第一个记录为枢轴记录。
    /*枢轴记录的选择有三策略:1.选区间第一个元素;2.在区间中随机选一个元素;3.三者取中法(选区间
    头尾和中间的三个元素中的中位数(中值)为枢轴记录,这样有利于应该关键字基本有序或已序的情况*/
	while (low < high)
	{//升序排序
		while (low < high&&L[high] >= pivotkey)--high;
		L[low] = L[high];
		while (low < high&&L[low] <= pivotkey)++low;
		L[high] = L[low];
	}
	L[low] = pivotkey;//枢轴记录归位
	return low;//返回枢轴记录的位置
}

void QSort(vector<int>& L, int low, int high)
{//对顺序表L[low,...,high]的子序列做快速排序
	int pivotLoc;
	if (low < high)    //长度大于1
	{
		pivotLoc = Partition(L, low, high);
		QSort(L, low, pivotLoc - 1);
		QSort(L, pivotLoc + 1, high);
	}
}

void QuickSort(vector<int>& L)
{//对顺序表L做快速排序!
	QSort(L, 0, L.size() - 1);
}

下面对照一次划分函数int Partition(vector<int> &L, int low, int high)的代码:

给出序列{49  38  65  97  76  13  27  49}的快速排序过程,以此记住快排的代码含义!

一次划分:

high指针一直往前移动,找到第一个比pivotkey小的记录,然后赋到low指针位置处;

然后:

low指针一直往后移动,直到找到第一个比pivotkey大的记录,然后将其赋值到high指针所指位置。

这样算作完成一个high和low的交换。

重复上述过程,直到low==high为止(low  high相遇为止)

这样,一趟划分排序结束,枢轴位置将放置到最终正确的位置上。

然后递归的思想,再分别在枢轴值的左/右两边分别进行划分排序!

冒泡排序_快速排序(划分)_1求数组中第k大的数_2求数组中出现次数超过一半的数_3求数组中最小的k个数

3.2模板函数:std::partition :将迭代器范围内满足谓词判断的元素移至前面。

function template

<algorithm>

std::partition

参考:[1]http://www.cplusplus.com/reference/algorithm/partition/?kw=partition

template<class _FwdIt,
	class _Pr> inline
	_FwdIt partition(_FwdIt _First, _FwdIt _Last, _Pr _Pred)
	{	// move elements satisfying _Pred to beginning of sequence
	_DEBUG_RANGE(_First, _Last);
	_DEBUG_POINTER(_Pred);
	return (_Rechecked(_First,
		_Partition(_Unchecked(_First), _Unchecked(_Last), _Pred,
			_Iter_cat(_First))));
	}

C++11标准函数原型:

template <class ForwardIterator, class UnaryPredicate>
  ForwardIterator partition (ForwardIterator first,
                             ForwardIterator last, UnaryPredicate pred);

 函数功能:

Partition range in two

Rearranges the elements from the range [first,last), in such a way that all the elements for which pred returns trueprecede all those for which it returns false. The iterator returned points to the first element of the second group.

C++98实现方式参考:

template <class BidirectionalIterator, class UnaryPredicate>
  BidirectionalIterator partition (BidirectionalIterator first,
                                   BidirectionalIterator last, UnaryPredicate pred)
{
  while (first!=last) {
    while (pred(*first)) {
      ++first;
      if (first==last) return first;
    }
    do {
      --last;
      if (first==last) return first;
    } while (!pred(*last));
    swap (*first,*last);
    ++first;
  }
  return first;
}

 划分函数小例子:将向量元素中的奇数移动到向量的前面,偶数移动到向量的后面,不保证元素间原来的顺序。

// partition algorithm example
#include <iostream>     // std::cout
#include <algorithm>    // std::partition
#include <vector>       // std::vector

bool IsOdd (int i) { return (i%2)==1; }

int main () {
  std::vector<int> myvector;

  // set some values:
  for (int i=1; i<10; ++i) myvector.push_back(i); // 1 2 3 4 5 6 7 8 9

  std::vector<int>::iterator bound;
  bound = std::partition (myvector.begin(), myvector.end(), IsOdd);

  // print out content:
  std::cout << "odd elements:";
  for (std::vector<int>::iterator it=myvector.begin(); it!=bound; ++it)
    std::cout << ' ' << *it;
  std::cout << '\n';

  std::cout << "even elements:";
  for (std::vector<int>::iterator it=bound; it!=myvector.end(); ++it)
    std::cout << ' ' << *it;
  std::cout << '\n';

  return 0;
}

程序输出:
odd elements: 1 9 3 7 5
even elements: 6 4 8 2

3.2.2  stable_partition  :划分前后元素的相对前后关系保持不变。

function template

<algorithm>

std::stable_partition

// stable_partition example
#include <iostream>     // std::cout
#include <algorithm>    // std::stable_partition
#include <vector>       // std::vector

bool IsOdd (int i) { return (i%2)==1; }

int main () {
  std::vector<int> myvector;

  // set some values:
  for (int i=1; i<10; ++i) myvector.push_back(i); // 1 2 3 4 5 6 7 8 9

  std::vector<int>::iterator bound;
  bound = std::stable_partition (myvector.begin(), myvector.end(), IsOdd);

  // print out content:
  std::cout << "odd elements:";
  for (std::vector<int>::iterator it=myvector.begin(); it!=bound; ++it)
    std::cout << ' ' << *it;
  std::cout << '\n';

  std::cout << "even elements:";
  for (std::vector<int>::iterator it=bound; it!=myvector.end(); ++it)
    std::cout << ' ' << *it;
  std::cout << '\n';

  return 0;
}

输出:
odd elements: 1 3 5 7 9
even elements: 2 4 6 8

 

 

4.求数组中第k大的数(利用快速排序一次划分的思路:利用上面的Partition()函数)

一次划分结束得到枢轴值的最终位置:pivotLoc

若pivotLoc==n-k  (升序排序,第k大的数最终就在下标位n-k的位置上),那么L[pivotLoc]就是要求的第k大的数;

若pivotLoc < n - k 则说明枢轴值的位置在第k大的数的位置(n-k)的前面,即第k大的数在枢轴值位置的右边,那么继续在枢轴值位置的右侧子序列中找第k大的数(递归);

若pivotLoc > n - k  则说明枢轴值的位置在第k大的数在最终位置的后面(右侧),则第k的的数在枢轴位置的左侧,一个在左侧子序列中寻找第k - (n - pivotLoc)大的数。

比如序列          {49  38   65  97   76  13  27  49}

经过一次划分:{27  38  13}  49  {76  97  65  49}

枢轴值49放到了第5大的数的最终位置上,我要找第6大的数,即是要在左子序列中找第(6-(8-3)=1)大的数

只要清楚:升序情况下:下标为n-k的数是第k大的数,

若pivotLoc > n - k ,则k>n-pivotLoc,则原数组第k大的数就是左子序列第[k-(n-pivotLoc)]大的数。

 

int getkthMaxNumber(vector<int> L,int k)
{
	int n = L.size();
	if (n<=0||k <= 0 || k > n)
        {//throw "bad arguments";
            throw new std::exception("Invalid Parameters");
        }
	int pivotLoc = Partition(L, 0, n - 1);
	if (pivotLoc == n - k)
	{
		return L[pivotLoc];
	}
	else if (pivotLoc < n - k)
	{//升序情况下,在右半部分仍是第k大的数
		vector<int> right(L.begin() + pivotLoc + 1, L.end());
		return getkthMaxNumber(right, k);
	}
	else
	{//在左半部分则是第k-(n-mid)大的数
		vector<int> left(L.begin(), L.begin() + pivotLoc);
		return getkthMaxNumber(left,k - (n - pivotLoc));
	}
}

 下面是以上 冒泡排序、快速排序、get数组中第k大的数的测试例子:

#include "stdafx.h"
#include<iostream>
#include<vector>

using namespace std;


int _tmain(int argc, _TCHAR* argv[])
{
	int myArray[5] = { 3, 6, 1, 2, 7 };
	vector<int> myVec(myArray, myArray + 5);
	//cout << "before sort,myVec:" << endl;
	//for (auto element : myVec)
	//{
	//	cout << element << " ";
	//}
	//cout << endl;

	//bubbleSort<int>(myVec);
	//QuickSort(myVec);


	//cout << "after sort,myVec:" << endl;
	//for (auto element : myVec)
	//{
	//	cout << element << " ";
	//}
	//cout << endl;

	int kthMaxNum = getkthMaxNumber(myVec, 5);
	cout << "kthMaxNum=" << kthMaxNum << endl;

	system("pause");
	return 0;
}

5.已序顺序表二分查找循环实现:

//顺序表的二分查找:循环实现。
int BinarySearchLoop(vector<int> vec, int low, int high,int key)
{
	int mid;
	while (low <= high)
	{
		mid = (low + high) / 2;
		if (vec[mid] == key)
		{
			return mid;
		}
		else if (vec[mid] < key)
		{
			high = mid - 1;
		}
		else
		{
			low = mid + 1;
		}
	}
	return -1;//没有找到则返回-1。
}

6.已序顺序表二分查找递归实现

//顺序表的二分查找:递归实现
int BinarySearchRecursion(vector<int> vec, int low, int high, int key)
{
	int mid = -1;
	if (low <= high)
	{
		mid = (low + high) / 2;
		if (vec[mid] < key)
		{
			mid = BinarySearchRecursion(vec, mid + 1, high, key);
		}
		else if (vec[mid] > key)
		{
			mid = BinarySearchRecursion(vec, low, high - 1, key);
		}
		else
		{
			return mid;
		}
	}
	return -1;//没找到,则返回-1
}

已序顺序表的二分查找测试小李子:

#include "stdafx.h"
#include<iostream>
#include<vector>

using namespace std;


int _tmain(int argc, _TCHAR* argv[])
{
	int myArray[5] = { 3, 6, 1, 2, 7 };
	vector<int> myVec(myArray, myArray + 5);

	//bubbleSort<int>(myVec);
	QuickSort(myVec);

	//int kthMaxNum = getkthMaxNumber(myVec, 5);
	//cout << "kthMaxNum=" << kthMaxNum << endl;

	int key = 5;
	int keyIndex = BinarySearchLoop(myVec, 0, myVec.size() - 1, key);
	//int keyIndex = BinarySearchRecursion(myVec, 0, myVec.size() - 1, key);
	cout << "key=" << key << "'s index is " << keyIndex << endl;

	system("pause");
	return 0;
}

 

7.求数组中出现次数超过一半的数


/*
《剑指offer》第5章优化时间和空间效率
P205面试题39:get数组中出现次数超过数组长度一半的数字
如{1,2,3,2,2,2,5,4,2}
则输出2
解法2:根据该类数组的特点,找出时间复杂度为O(n)的算法

在遍历数组的时候保存两个值:一个是数组中的数字,另一个是次数
当我们遍历到下一个数字时,如果下一个数字和我们之前保存的数字不同,则次数减1
如果下一个数字和我们之前保存的数字相同,则次数加1,
如果次数为0,则我们需要更新该数字,存入数组中对应的一个新的数,并设次数为1;

基于这样一个事实:我们要找的数字出现的次数比其他所有数字出现的次数的总和还要多,
那么要找的数字肯定时最后一次把次数设为1时对应的数字!

另外:要加一个检查函数CheckMoreThanHalf()函数,来确认该数字出现的次数确实超过了数组长度的一半!
*/


bool CheckIsMoreThanHalf(vector<int> vec, int number)
{
	if (vec.size() == 0)
		throw new std::exception("Invalid Paramters!");
	int times = 0;
	for (int i = 0; i < vec.size(); i++)
	{
		if (vec[i] == number)
		{
			times++;
		}
	}
	bool isMoreThanHalf = true;
	if (times <= vec.size() / 2)
	{
		isMoreThanHalf = false;
	}
	return isMoreThanHalf;
}

int getMoreThanHalfNum(vector<int> vec)
{
	if (vec.size() == 0)
	{
		throw new std::exception("Invalid Paramters!");
	}
	int result = vec[0];
	int times = 1;
	for (int i = 1; i < vec.size(); i++)
	{
		if (times == 0)
		{
			result = vec[i];
			times = 1;
		}
		else if (vec[i] == result)
		{
			times++;
		}
		else
		{
			times--;
		}
	}
	if (!CheckIsMoreThanHalf(vec, result))
	{
		throw new std::exception("Not exist!");
	}
	else
	{
		return result;
	}
}

测试小栗子:

#include "stdafx.h"
#include<iostream>
#include<vector>

using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
	int Array[9] = { 1, 2, 3, 2, 2, 2, 5, 4, 2 };
	vector<int> vec(Array, Array + 9);
	int moreThanHalfOfNum = getMoreThanHalfNum(vec);
	cout << "more than half of vec's length number is " << moreThanHalfOfNum << endl;

	system("pause");
	return 0;
}

7.2一个小小错误滴bug版本:假设数组中的所有数字都是个位数!

哈希表是以空间换时间的绝配!

/*
《剑指offer》第5章优化时间和空间效率
P205面试题39:get数组中出现次数超过数组长度一半的数字
如{1,2,3,2,2,2,5,4,2}
则输出2
我的思路:利用哈希表统计的思维,时间赋值度O(n),空间复杂度O(1)

bug:只能针对个位数!
这个数组中数字的范围是已知的!

所以这个可以反问面试官:数组中的数字是在一定范围内的数字吗?
因为现实世界中我们做统计,数据肯定是有限范围内的数字!
*/

int getMoreThanHalfNumBugEdition(vector<int> vec)
{
	if (vec.size() == 0)
	{
		throw new std::exception("Invalid Parameters");
	}
	static int hashTableSize = 10;//0~9:10个数字
	int* hashTable = new int[hashTableSize]();

	for (int i = 0; i < vec.size(); i++)
	{
		hashTable[vec[i]]++;
	}

	for (int i = 0; i < 10; i++)
	{
		if (hashTable[i]>vec.size() / 2)
		{
			return i;
		}
	}
	throw new std::exception("more than half of vector's length number is not exist!");
}

8.求数组中最小的k个数(法1:基于快排划分思路;法2:基于大顶堆容器,流式(循环读入)处理海量数据的思路)

法1:基于快速排序划分的思路:

/*

基于数组的第k个数字来进行调整,使得比第k个数字小的所有数字都位于数组的左边,比第k个数字大的数字都位于数字的右边。

经过这样的调整后,位于数字左边的k个数字就是最小的k个数字

*/

1.在看一遍快速排序Partition()函数:

/*对顺序表L的快速排序的一次划分函数:*/
int Partition(vector<int> &L, int low, int high)
{
	int pivotkey = L[low];//设立枢轴记录
	while (low < high)
	{
		while (low < high&&L[high] >= pivotkey)--high;
		L[low] = L[high];
		while (low < high&&L[low] <= pivotkey)++low;
		L[high] = L[low];
	}
	L[low] = pivotkey;//枢轴记录归位
	return low;//返回枢轴记录的位置
}

2.

/*
8.求数组中最小的k个数(法1:基于快排划分思路;法2:基于大顶堆容器,流式(循环读入)处理海量数据的思路)
法1:基于快速排序划分的思路:

基于数组的第k个数字来进行调整,使得比第k个数字小的所有数字都位于数组的左边,比第k个数字大的数字都位于数字的右边。

经过这样的调整后,位于数字左边的k个数字就是最小的k个数字
*/

void getLeastKthNumber(vector<int> vec, vector<int>& ans, int k)
{
	if (vec.size() == 0 || ans.size() != 0 || k > vec.size() || k <= 0)
	{
		return;
	}
	int low = 0;
	int high = vec.size()-1;//注意这里下标不要越界!
	int index = Partition(vec, low, high);
	while (index != (k - 1))
	{
		if (index > k - 1)
		{
			high = index - 1;
			index = Partition(vec, low, high);
		}
		else
		{
			low = index + 1;
			index = Partition(vec, low, high);
		}
	}
	for (int i = 0; i < k; i++)
	{
		ans.push_back(vec[i]);
	}
}

下面是测试小栗子:

int _tmain(int argc, _TCHAR* argv[])
{
	int Array[9] = { 1, 2, 3, 2, 2, 2, 5, 4, 2 };
	vector<int> vec(Array, Array + 9);

	vector<int> ans;
	getLeastKthNumber(vec, ans, 4);
	for (int i = 0; i < 4; i++)
	{
		cout << ans[i] << " ";
	}
	cout << endl;

	system("pause");
	return 0;
}

8.2

8.求数组中最小的k个数(法1:基于快排划分思路;法2:基于大顶堆容器,流式(循环读入)处理海量数据的思路)
/*
用一个大顶堆数据容器来装前k个最小的数。

STL中的set和multiset是基于红黑树实现的堆,可设置为大顶堆。


*/


/*
用一个大顶堆数据容器来装前k个最小的数。
*/
void getLeastKthNumber_bigRootHeap_multiset(vector<int> vec, vector<int>& ans, int k)
{

	/*
	#include<set>
	#include<functional>
	*/
	 multiset<int, greater<int>> intSet;
	 multiset<int, greater<int>>::iterator iterSet;
	 ans.clear();
	 if (k < 1 || vec.size() < k)
		 throw new std::invalid_argument("参数异常!");

	 vector<int>::iterator iterVec = vec.begin();
	 for (; iterVec != vec.end(); ++iterVec)
	 {
		 if (intSet.size() < k)
		 {
			 intSet.insert(*iterVec);
		 }
		 else
		 {
			 iterSet = intSet.begin();
			 if (*iterVec < *(iterSet))
			 {
				 intSet.erase(iterSet);
				 intSet.insert(*iterVec);
			 }
		 }
	 }
	 for (iterSet = intSet.begin(); iterSet != intSet.end(); iterSet++)
	 {
		 ans.push_back(*iterSet);//从大到小排序!
	 }
}

测试小栗子:

#include "stdafx.h"
#include<iostream>
#include<vector>
#include<set>
#include<functional>
#include<algorithm>

using namespace std;


int _tmain(int argc, _TCHAR* argv[])
{
	int Array[9] = { 1, 5, 3, 4, 6, 3, 2, 2, 7 };
	vector<int> vec(Array, Array + 9);
	////int moreThanHalfOfNum = getMoreThanHalfNum(vec);
	//int moreThanHalfOfNum = getMoreThanHalfNumBugEdition(vec);
	//cout << "more than half of vec's length number is " << moreThanHalfOfNum << endl;

	vector<int> ans;
	//getLeastKthNumber(vec, ans, 4);
	getLeastKthNumber_bigRootHeap_multiset(vec, ans, 4);
	for (int i = 0; i < 4; i++)
	{
		cout << ans[i] << " ";
	}
	cout << endl;

	system("pause");
	return 0;
}

 

冒泡排序 快速排序 第k大的数  已序顺序表的二分查找