常见排序算法汇总(C++实现)

目录

插入排序

冒泡排序

快速排序(普通版AND改进版)

简单选择排序

堆排序

二路归并排序

希尔排序

基数排序

排序算法分析

Hash表的实现(拉链法)

单链表排序

快速排序

二分查找 

外部归并排序法


插入排序

#include<iostream>
using namespace std;
/*
插入排序的细节讲解与复杂度分析
时间复杂度O(N ^ 2),额外空间复杂度O(1)
*/
void InsertSort(int *arr, int length)
{
	int i, j;
	int temp;
	if (arr == nullptr || length < 2)
		return;
	for (i = 1; i < length; ++i)
	{
		temp = arr[i];
		j = i - 1;
		while (j >= 0 && temp < arr[j])
		{
			arr[j + 1] = arr[j];
			--j;
		}
		//找到插入位置,将temp中待排元素插入
		arr[j + 1] = temp;
	}
}

int main(void)
{
	int arr[4] = { 4,3,2,1 };
	InsertSort(arr, 4);
	cout << arr[0] << arr[1] << arr[2] << arr[3] << endl;
	system("pause");
	return 0;
}

冒泡排序

#include<iostream>
using namespace std;
//将大的筛选筛选到后边
void BubbleSort(int *arr, int length)
{
	if (arr == nullptr || length < 2)
		return;
	int flag;//标记
	int temp;
	for (int i = length - 1; i >= 1; --i)
	{
		flag = 0;//变量flag用来标记本趟排序是否发生了交换
		for (int j = 1; j <= i; ++j)
		{
			if (arr[j - 1]>arr[j])
			{
				temp = arr[j];
				arr[j] = arr[j - 1];
				arr[j - 1] = temp;
				//如果未发生交换,则flag的值为0;如果发生交换,则flag的值为1
				flag = 1;
			}
		}
		//一趟排序过程中没有发生元素交换,则证明序列有序,排序结束
		if (flag == 0)
			return;
	}
}
int main(void)
{
	int arr[2] = { 5,0 };
	BubbleSort(arr, 2);
	for (auto i : arr)
		cout << i << " ";
	cout << endl;
	system("pause");
	return 0;
}

快速排序(普通版AND改进版)

快速排序思想

通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

快排不稳定的原因在于需要随机选一个数作为待比较的数,比如{8,5,5,3,6},选第一个或者是第二个5时就会引用不稳定。

#include<iostream>
using namespace std;
int patition(int *arr, int left, int right)
{
	int i = left;
	int j = right - 1;
	while (i <= j)
	{
		while (arr[i] < arr[right])
			++i;
		while (arr[j] > arr[right] && j > i)
			--j;
		if (i < j)
			swap(arr[i], arr[j]);
		else
			break;
	}
	swap(arr[i], arr[right]);
	return i;
}
void quickSort(int *arr, int left, int right)
{
	if (arr == nullptr || left >= right)
		return;
	int value = rand() % (right - left + 1) + left;
	swap(arr[value], arr[right]);
	int mid = patition(arr, left, right);
	quickSort(arr, left, mid);
	quickSort(arr, mid + 1, right);
	return;
}
int main(void)
{
	int arr[5] = { 4,3,5,1,7 };
	quickSort(arr, 0, 4);
	cout << arr[0] << arr[1] << arr[2] << arr[3] << arr[4] << endl;
	system("pause");
	return 0;
}

非递归版本

既然不能递归,只能利用占进行存储,然后继续调用patition函数。

void quickSortNonRecursive(int *nums, int low, int height)
{
	stack<int> s;
	if (low<height)
	{
		int mid = getPartition(nums, low, height);
		if (mid - 1>low)
		{
			s.push(low);
			s.push(mid - 1);
		}
		if (mid + 1<height)
		{
			s.push(mid + 1);
			s.push(height);
		}

		while (!s.empty())
		{
			int qHeight = s.top();
			s.pop();
			int pLow = s.top();
			s.pop();
			int pqMid = getPartition(nums, pLow, qHeight);
			if (pqMid - 1 > pLow)
			{
				s.push(pLow);
				s.push(pqMid - 1);
			}
			if (pqMid + 1 < qHeight)
			{
				s.push(pqMid + 1);
				s.push(qHeight);
			}
		}
	}
}

荷兰国旗版本 

#include<iostream>
#include<ctime>
using namespace std;
#define random(x)(rand()%x)
int p1, p2;
//快速排序
/*
在经典快排的基础上,做了两个改动
1.不是选择数组的最后一个数,而是随机选择一个
2.经典的是以一个数为中间,然后左边小于等于,右边大于
而现在是左边小于的数组,中间全部是等于这个数的数组,
右边是大于的数组
*/
void swap(int *arr, int i, int j);
void partition(int *arr, int l, int r);
void QuickSort(int *arr, int l, int r)
{
	if (l < r)
	{
		srand((int)time(0));
		/*
		经典快排是选择数组的最后一个数,现在改进的
		随机快排多了随机选择数组中的一个数作为比较对象,
		不一定非得是最后一个数
		*/
		swap(arr, random(r - l + 1) + l, r);
		partition(arr, l, r);
		QuickSort(arr, l, p1 - 1);
		QuickSort(arr, p2 + 1, r);
	}
}
void partition(int *arr, int l, int r)
{
	int less = l - 1;
	int more = r;
	while (l < more)
	{
		if (arr[l] < arr[r])
		{
			swap(arr, ++less, l++);
		}
		else if (arr[l]>arr[r])
		{
			swap(arr, --more, l);
		}
		else
		{
			l++;
		}
	}
	//more是>=r的第一个位置,所以最后将r放到它该在的位置
	swap(arr, more, r);//将最后一位作为比较的数移到它该在的位置
	p1 = less + 1;//等于arr[r]的第一个元素
	p2 = more;//等于arr[r]的最后一个元素
}

void swap(int *arr, int i, int j)
{
	int temp = arr[i];
	arr[i] = arr[j];
	arr[j] = temp;
}

int main(void)
{
	int i_arr[] = { 2,6,4,8,1,2,8,3 };
	QuickSort(i_arr, 0, 7);
	for (int i : i_arr)
		cout << i << "	";
	cout << endl;
	system("pause");
	return 0;
}

简单选择排序

#include<iostream>
using namespace std;
void SelectSort(int *arr, int length)
{
	int index, temp;
	for (int i = 0; i < length - 1; ++i)
	{
		index = i;//当前的最小元素
		//从无序数组中挑出一个最小的元素
		for (int j = i + 1; j <= length - 1; ++j)
			if (arr[j] < arr[index])
				index = j;
		//完成最小元素与无序序列中第一个元素的交换
		temp = arr[i];
		arr[i] = arr[index];
		arr[index] = temp;
	}
}
int main(void)
{
	int arr[6] = { 6,5,4,3,2,1 };
	SelectSort(arr, 6);
	for (auto i : arr)
		cout << i << " ";
	cout << endl;
	system("pause");
	return 0;
}

堆排序

堆排序的基本思想:

将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆(通过将堆顶元素不断下沉实现),这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了

#include<iostream>
using namespace std;
void Sift(int *arr, int k, int length)
{
	int left = k * 2 + 1;
	int right = k * 2 + 2;
	int min = k;
	if (left < length && arr[left] > arr[min])
		min = left;
	if (right < length && arr[right] > arr[min])
		min = right;
	if (min != k)
	{
		swap(arr[min], arr[k]);
		Sift(arr, min, length);
	}
	return;
}
void heapSort(int *arr,int length)
{
	if (arr == nullptr || length < 1)
		return;
	for (int i = (length - 1)/2; i >= 0; --i)
		Sift(arr, i, length);
	for (int i = length - 1; i >= 1; --i)
	{
		swap(arr[0], arr[i]);
		Sift(arr, 0, i);
	}
	return;
}
int main(void)
{
	int arr[8] = { 8,4,7,1,9,3,0,2 };
	heapSort(arr, 8);
	cout << arr[0] << arr[1] << arr[2] << arr[3] << arr[4] << endl;
	cout << arr[5] << arr[6] << arr[7] << endl;
	system("pause");
	return 0;
}

二路归并排序

#include<iostream>
using namespace std;
/*
该函数实现的是合并两个有序子序列
*/
void Merge(int *arr, int low, int high, int mid)
{
	int num1 = mid - low + 1;
	int num2 = high - mid;
	//将左半部分的有序子序列存到数组left中
	int *left = new int[num1];
	//将右半部分的有序子序列存到数组right中
	int *right = new int[num2];
	int i, j, k;
	for (i = 0; i < num1; ++i)
		left[i] = arr[low + i];
	for (j = 0; j < num2; ++j)
		right[j] = arr[mid + 1 + j];

	//比较两个有序数组中的元素,每次都在找较小的元素
	i = 0, j = 0, k = low;
	while (i < num1 && j < num2)
	{
		if (left[i] < right[j])
			arr[k++] = left[i++];
		else
			arr[k++] = right[j++];
	}
	//如果某个数组中还有元素,则把剩余部分全部拷入arr中,
	//注意,不可能两个数组都还有元素
	while (i < num1)
		arr[k++] = left[i++];
	while (j < num2)
		arr[k++] = right[j++];

	delete[]left;
	delete[]right;
}
/*
二路归并排序
递归的方式进行,总是对两组有序数组进行排序
*/
void MergeSort(int *arr, int low, int high)
{
	if (low < high)
	{
		int mid = (low + high) >> 1;
		MergeSort(arr, low, mid);
		MergeSort(arr, mid + 1, high);
		Merge(arr, low, high, mid);
	}
}
int main(void)
{
	int arr[6] = {2,4,3,2,6,1};
	MergeSort(arr, 0, 5);
	for (auto i : arr)
		cout << i << " ";
	cout << endl;
	system("pause");
	return 0;
}

希尔排序

希尔排序的每趟排序,都会使整个序列变得更加有序,等整个序列基本有序了,再来一趟直接插入排序,这样会使排序效率更高,这就是希尔排序的思想。

#include<iostream>
#include<vector>
using namespace std;
void print(const vector<int>& data);
void ShellInsert(vector<int>&data, int num)
{
	for (int i = num; i < data.size(); ++i)
	{
		//说明需要进行调整,其实这部分就相当于是插入排序
		if (data[i] < data[i - num])
		{
			int temp = data[i];
			int j;

			for (j = i - num; j >= 0 && temp < data[j]; j -= num)
				data[j + num] = data[j];
			data[j + num] = temp;
		}
	}
	cout << "delta" << num << ":";
	print(data);
	cout << endl;
}
/*
data是待排序的数组
dalta是增量数组,可以由自己设定,一般5、3、1
*/
void ShellSort(vector<int>&data, vector<int>&delta)
{
	for (int i = 0; i < delta.size(); ++i)
		ShellInsert(data, delta[i]);
}
void print(const vector<int>& data)
{
	for (auto i : data)
		cout << i << " ";
}
int main(void)
{
	vector<int> data{ 2,4,8,5,3,9,6 };
	vector<int> delta{ 5,3,1 };
	ShellSort(data, delta);
	system("pause");
	return 0;
}

基数排序

算法思想:基数排序又称为“桶子法”,从低位开始将待排序的数按照这一位的值放到相应的编号为0~9的桶中。等到低位排完得到一个子序列,再将这个序列按照次低位的大小进入相应的桶中,一直排到最高位为止,数组排序完成。

#include<iostream>
using namespace std;
/*
求一个数组中的元素的最大位数,以决定排序次数
*/
int MaxBit(int *data, int length)
{
	int d = 1;//保存最大的位数
	int p = 10;
	for (int i = 0; i < length; ++i)
	{
		while (data[i] > p)
		{
			p *= 10;
			++d;
		}
	}
	//返回最大位数,比如数组中最大的元素是345,则返回的位数为3
	return d;
}
//基数排序
void RadixSort(int *data, int length)
{
	int d = MaxBit(data, length);
	//从桶0-9,存放这些桶中的元素,相当于是已经排序依次以后的顺序
	int *bucket = new int[length]; 
	//桶中元素的计数器,count[0]表示第0个桶中存放的元素个数,依次类推
	int *count = new int[10];
	int i, j, k;
	int radix = 1;
	//最大位数是几,就要进行几次怕排序
	for (i = 1; i <= d; ++i)
	{
		for (j = 0; j < 10; ++j)
			count[j] = 0;//每次分配前都要清空计数器
		for (j = 0; j < length; ++j)
		{
			k = (data[j] / radix) % 10;//统计每个桶中元素的个数,第一轮统计个位,第二轮十位,以此类推
			count[k]++;//保存到计数器中
		}
		/*
		这个是难点,在这卡了10分钟,count[i]表示第i个桶的右边界索引
		*/
		for (j = 1; j < 10; ++j)
			count[j] += count[j - 1];
		//从后向前放,比如个位数相同,后出现的在后面放,保持了稳定性
		//bucket数组中的数就是这一轮排序之后的依次呈现
		for (j = length - 1; j >= 0; --j)
		{
			k = (data[j] / radix) % 10;
			bucket[count[k] - 1] = data[j];
			count[k]--;
		}
		//基数排序的收集,把桶中的数据再倒出来,然后进行前一位数的排序
		for (j = 0; j < length ; ++j)
			data[j] = bucket[j];

		for (int y = 0; y < length; ++y)
			cout << data[y] << " ";
		cout << endl;
		radix *= 10;
	}
	delete[]bucket;
	delete[]count;
}
int main(void)
{
	int arr[5] = { 2,567,238,904,123};
	RadixSort(arr, 5);
	for (auto i : arr)
		cout << i << " ";
	cout << endl;
	system("pause");
	return 0;
}

排序算法分析

n为元素的个数,d为关键字的位数,rd为桶数。

快速排序和堆排序的使用场景比较

(1)若n较小(如n≤50),可采用直接插入或直接选择排序。

当记录规模较小时,直接插入排序较好;否则因为直接选择移动的记录数少于直接插人,应选直接选择排序为宜。

(2)若文件初始状态基本有序(指正序),则应选用直接插人、冒泡或随机的快速排序为宜;

(3)若n较大,则应采用时间复杂度为O(nlgn)的排序方法:快速排序、堆排序或归并排序。

快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
堆排序所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况。这两种排序都是不稳定的。
若要求排序稳定,则可选用归并排序。

堆排序的使用场景?

堆排序比较和交换次数比快速排序多,所以平均而言比快速排序慢,如果你需要的是排序,那么绝大多数场合都应该使用快速排序而不是其它O(nlogn)算法。

但有时候你要的不是“排序”,而是另外一些与排序相关的东西,比如最大/小的元素,topK之类,这时候堆排序的优势就出来了。用堆排序可以在N个元素中找到top K,时间复杂度是O(N log K),空间复杂的是O(K),而快速排序的空间复杂度是O(N),也就是说,如果你要在很多元素中找很少几个top K的元素,或者在一个巨大的数据流里找到top K,快速排序是不合适的,堆排序更省地方。

另外一个适合用heap的场合是优先队列,需要在一组不停更新的数据中不停地找最大/小元素,快速排序也不合适。

为什么快排平均情况下最快?

 在堆排序(小根堆)的时候,每次总是将最小的元素移除,然后将最后的元素放到堆顶,再让其自我调整。这样一来,有很多比较将是被浪费的,因为被拿到堆顶的那个元素几乎肯定是很大的,而靠近堆顶的元素又几乎肯定是很小的,最后一个元素能留在堆顶的可能性微乎其微,最后一个元素很有可能最终再被移动到底部。在堆排序里面有大量这种近乎无效的比较。

在快速排序中,每次数据移动都意味着该数据距离它正确的位置越来越近,而在堆排序中,类似将堆尾部的数据移到堆顶这样的操作只会使相应的数据远离它正确的位置,后续必然有一些操作再将其移动,即“做了好多无用功”。

插入、删除、查找时间复杂度

Hash表的实现(拉链法)

#include <iostream>
using namespace std;
typedef struct node {
	char *name;//字段名
	char *desc;//描述
	struct node *next;
}node;

#define HashSIZE 100 //Hash表长度
//定义一个Hash数组,该数组的每个元素是一个Hash结点指针,
//并且由于是全局静态变量,默认初始化为NULL
static node* Hashtable[HashSIZE];
unsigned int Hash(char *s)
{//哈希函数
	unsigned int h = 0;
	for (; *s; s++)
		h = *s + h * 31;//将整个字符串按照特定关系转化为一个整数,然后对Hash长度取余
	return h%HashSIZE;
}
node* lookup(char *str)
{
	unsigned int Hashvalue = Hash(str);
	node* np = Hashtable[Hashvalue];
	for (; np != NULL; np = np->next)
	{//这里是链地址法解决的冲突,返回的是第一个链表结点
		if (!strcmp(np->name, str))//strcmp相等的时候才返回0
			return np;
	}
	return NULL;
}
char* search(char* name)
{//对Hash表查找特定元素(元素是字符串)
	node* np = lookup(name);
	if (np == NULL)
		return NULL;
	else
		return np->desc;
}
node* malloc_node(char* name, char* desc)
{//在堆上为结点分配内存,并填充结点
	node *np = (node*)malloc(sizeof(node));
	if (np == NULL)
		return NULL;
	np->name = name;
	np->desc = desc;
	np->next = NULL;
	return np;
}
int insert(char* name, char* desc)
{
	unsigned int Hashvalue;
	Hashvalue = Hash(name);
	//头插法,不管该Hash位置有没有其他结点,直接插入结点
	node* np = malloc_node(name, desc);
	if (np == NULL) return 0;//分配结点没有成功,则直接返回
	np->next = Hashtable[Hashvalue];
	Hashtable[Hashvalue] = np;
	return 1;
}
void deleteNode(char* name)
{
	unsigned int Hashvalue;
	Hashvalue = Hash(name);
	node *np = lookup(name);
	if (np == NULL)
		return;
	np = Hashtable[Hashvalue];
	node *pre = NULL;
	while (strcmp(np->name,name))
	{
		pre = np;
		np = np->next;
	}
	if (pre == NULL)
		Hashtable[Hashvalue] = np->next;
	else
		pre->next = np->next;
	delete np;
	np = nullptr;
	return;
}
//显示Hash表元素(不包括空)
void displayHashTable()
{
	node *np;
	unsigned int Hashvalue;
	for (int i = 0; i < HashSIZE; ++i)
	{
		if (Hashtable[i] != NULL)
		{
			np = Hashtable[i];
			printf("\nHashvalue: %d (", i);
			for (; np != NULL; np = np->next)
				printf(" (%s.%s) ", np->name, np->desc);
			printf(")\n");
		}
	}
}
void cleanUp()
{//清空Hash表
	node *np, *tmp;
	for (int i = 0; i < HashSIZE; ++i)
	{
		if (Hashtable[i] != NULL)
		{
			np = Hashtable[i];
			while (np != NULL)
			{
				tmp = np->next;
				//free(np->name);
				//free(np->desc);
				free(np);
				np = tmp;
			}
		}
	}
}
int main()
{
	char* names[] = { "First Name","Last Name","address","phone","k101","k110" };
	char* descs[] = { "Kobe","Bryant","USA","26300788","Value1","Value2" };

	for (int i = 0; i < 6; ++i)
		insert(names[i], descs[i]);
	printf("we should see %s\n", search("k110"));
	insert("phone", "9433120451");//这里计算的Hash是冲突的,为了测试冲突情况下的插入
	printf("we have %s and %s\n", search("k101"), search("phone"));
	displayHashTable();
	deleteNode("address");
	deleteNode("First Name");
	cout << endl;
	displayHashTable();
	cleanUp();
	system("pause");
	return 0;
}

单链表排序

归并法(O(nlgn))

struct ListNode {
	int val;
	ListNode *next;
	ListNode(int x) : val(x), next(NULL) {}
};
ListNode *sortList(ListNode *head)
{
	if (head == nullptr || head->next == nullptr)
		return head;
	ListNode *slow = head;
	ListNode *fast = head;
	ListNode *pre = head;
	while (fast && fast->next)
	{
		pre = slow;
		slow = slow->next;
		fast = fast->next->next;
	}
	pre->next = nullptr;
	return merge(sortList(head), sortList(slow));
}
ListNode *merge(ListNode *l1, ListNode *l2)
{
	ListNode *dummy = new ListNode(-1);
	ListNode *cur = dummy;
	while (l1 && l2)
	{
		if (l1->val < l2->val)
		{
			cur->next = l1;
			l1 = l1->next;
		}
		else
		{
			cur->next = l2;
			l2 = l2->next;
		}
		cur = cur->next;
	}
	if (l1) cur->next = l1;
	if (l2) cur->next = l2;
	return dummy->next;
}

快速排序(O(nlgn))

思路:

对于数组中的快排,需要一个指针指向头,一个指针指向尾,然后两个指针相向运动并按一定规律交换值,最后找到一个支点使得支点左边小于支点,支点右边大于支点。不过问题出来了。如果是这样的话,对于单链表我们没有前驱指针,怎么能使得后面的那个指针往前移动呢?所以这种快排思路行不通,如果我们能使两个指针都往next方向移动并且能找到支点那就好了。怎么做呢?

接下来我们使用快排的另一种思路来解答。我们只需要两个指针p和q,这两个指针均往next方向移动,移动的过程中保持p及之前的key都小于选定的key,p和q之间的key都大于选定的key,那么当q走到末尾的时候便完成了一次支点的寻找。

使用时只需调用QuickSort(pHead,NULL)即可!

struct ListNode {
	int val;
	ListNode *next;
	ListNode(int x) : val(x), next(NULL) {}
};
ListNode* sortList(ListNode* head)
{
	quickSort(head, nullptr);
	return head;
}
void quickSort(ListNode *begin, ListNode *end)
{
	if (begin != end)
	{
		ListNode *k = patition(begin, end);
		quickSort(begin, k);
		quickSort(k->next, end);
	}
	return;
}
ListNode* patition(ListNode* begin,ListNode *end)
{
	int value = begin->val;
	ListNode *node = begin->next;
	ListNode *index = begin;
	while (node != end)
	{
		if (node->val < value)
		{
			index = index->next;
			swap(index->val, node->val);
		}
		node=node->next;
	}
	swap(begin->val, index->val);
	return index;
}

 对单链表进行插入排序(O(n^2))

struct ListNode {
	int val;
	ListNode *next;
	ListNode(int x) : val(x), next(NULL) {}
};

ListNode* findInsertPos(ListNode* head, int value);
ListNode* insertionSortList1(ListNode* head)
{
	ListNode first(INT_MIN);
	//first.next = head;
	//调整插入前后指针的指向
	for (ListNode *cur = head; cur != nullptr;)
	{
		ListNode *pos = findInsertPos(&first, cur->val);
		ListNode *next = cur->next;
		cur->next = pos->next;
		pos->next = cur;
		cur = next;
	}
	return first.next;
}
//找到插入位置
ListNode* findInsertPos(ListNode* head, int value)
{
	ListNode *cur = head;
	ListNode *prev = nullptr;
	for (; cur != nullptr && cur->val <= value;prev=cur,cur=cur->next)
		;
	return prev;
}

二分查找 

#include<iostream>
using namespace std;
//非递归版本
int BinarySerach1(int *arr, int length,int value)
{
	if (arr == nullptr || length < 1)
		return -1;
	int left = 0;
	int right = length - 1;
	int mid = 0;
	while (left <= right)
	{
		mid = (left + right) / 2;
		if (arr[mid] == value)
			return mid;
		else if (arr[mid] > value)
			right = mid - 1;
		else
			left = mid + 1;
	}
	return -1;
}
//递归版本
int BinarySerach2Core(int *arr, int left, int right, int value);
int BinarySerach2(int *arr, int length, int value)
{
	if (arr == nullptr || length < 1)
		return - 1;
	return BinarySerach2Core(arr, 0, length - 1, value);
}
int BinarySerach2Core(int *arr, int left, int right, int value)
{
	if (left <= right)
	{
		int mid = (left + right) / 2;
		if (arr[mid] == value)
			return mid;
		else if (arr[mid] > value)
			return BinarySerach2Core(arr, left, mid - 1, value);
		else
			return BinarySerach2Core(arr, mid+1, right, value);
	}
	return -1;
}
int main(void)
{
	int arr[10] = { 1, 3, 5, 6, 7, 9, 11, 12, 14, 48 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int ret = BinarySerach2(arr, sz,100);
	printf("%d\n", ret);
	system("pause");
	return 0;
}

外部归并排序法

第一阶段:将文件中的数据分段输入到内存中,在内存中用内部排序方法对其分类,排序完的文件段成为归并段,然后将其写回到外存中,这样就在外存中形成了许多初始归并段。

第二阶段:有了m个初始归并段(都是有序段),便可以进行k路归并,重复进行,直到只生成一个段为止,这个段就是最后生成的归并段。

比如二路归并:

接下来把12个数据分成4份,然后排序成有序子串:

 然后把子串进行两两合并:

 输出哪个元素就在那个元素所在的有序子串再次读入一个元素:

 重复直到合并成一个包含6个int有序子串:

 再把两个包含6个int的有序子串合并成一个包含12个int数据的最终有序子串:

因为硬盘的读写速度比内存要慢的多,按照以上这种方法,每个数据都从硬盘读了三次,写了三次,要花很多时间。

解释下:例如对于数据2,我们把无序的12个数据分成有序的4个子串需要读写各一次,把2份3个有序子串合并成6个有序子串读写各一次;把2份6个有序子串合并从12个有序子串读写各一次,一共需要读写各3次。

在进行有序子串合并的时候,不采取两两合并的方法,而是可以3个子串,或4个子串一起来合并。

多路归并

 为了方便讲解,我们假设内存一共可以装4个int型数据。

刚才我们是采取两两合并的方式,现在我们可以采取4个有序子串一起合并的方式,这样的话,每个数据从硬盘读写的次数各需要2次就可以了。如图:

4个有序子串的合并,叫4路归并。如果是n个有序子串的合并,就把它称为n路归并。n并非越大越好。因为这样的话,为了得到比如上图中的2,比较次数也会增加。

代码实现

采用分治法归并排序,归并两个有序数组时间复杂度为O(n),将K个有序数组分治归并时间复杂度为O(logk),算法整体时间复杂度为O(nlogk)。

vector<int> mergeTwoArrays(const vector<int> &A, const vector<int> &B)
{
vector<int> temp;
temp.resize(A.size() + B.size());
int index = 0, i = 0, j = 0;
while (i < A.size() && j < B.size())
{
if (A[i] < B[j])
temp[index++] = A[i++];
else
temp[index++] = B[j++];
}
while (i < A.size())
temp[index++] = A[i++];
while (j < B.size())
temp[index++] = B[j++];
return temp;
}
vector<int> kMergeSort(const vector<vector<int>> &A, int start, int end)
{
if (start >= end)
return A[start];
int mid = start + (end - start) / 2;
vector<int> left = kMergeSort(A, start, mid);
vector<int> right = kMergeSort(A, mid+1, end);
return mergeTwoArrays(left, right);
}
vector<int> mergeSortArrays(const vector<vector<int>> &A)
{
vector<int> v;
if (A.size() == 0 || A[0].size() == 0)
return v;
return kMergeSort(A, 0, A.size() - 1);
}

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值