快速排序算法实验

实验目的

通过尝试多种方法,对基本的快速排序算法进行优化,并体会排序算法的多样性与可操作性。

实验原理

用不同算法对指定记录数的文件进行排序,经过一定数量的重复实验,取得每种算法的平均运行时长,进而比较各种算法的优劣。

一、步骤

1、从数列中挑出一个元素,称为"基准"(pivot)。
2、重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
3、递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

二、复杂度

快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为两个子序列(sub-lists)。平均需要O(log2n)趟,平均算法复杂度为O(nlogn),但最坏的情况下会变成O(n^2)。

最坏情况就是每次将一组数据划分为两组的时候,分界线都选在了边界上,使得划分了和没划分一样,最后就变成了普通的选择排序了。

实验内容


一、简单快速排序最坏情况(排列好的数列)

(一)完整代码

/* C implementation QuickSort */
#include<stdio.h>
#include<stdlib.h>
#include<time.h>

/* Function to print an array */
void printArray(int arr[], int size)
{
	int i;
	for (i = 0; i < size; i++)
		printf("%d ", arr[i]);
	printf("\n");
}

void swap(int *a, int *b)
{
	int temp;
	temp = *a;
	*a = *b;
	*b = temp;
}
int partition(int arr[], int low, int high)
{
	int pivot = arr[low];    // pivot, 中心值
	int i = low;
	int j = high;
	while (i < j)
	{
		while (i < j && arr[j] > pivot)
			j--;
		if (i < j)
		{
			arr[i] = arr[j];
			i++;
		}
		while (i < j && arr[i] <= pivot)
			i++;
		if (i < j)
		{
			arr[j] = arr[i];
			j--;
		}
	}
	arr[i] = pivot;
	return i;
}

/* The main function that implements QuickSort
 arr[] --> Array to be sorted,
  low  --> Starting index,
  high  --> Ending index */
void quickSort(int arr[], int low, int high)
{
	if (low < high)
	{
		/* pi is partitioning index, arr[p] is now
		   at right place */
		int pi = partition(arr, low, high);

		// Separately sort elements before 
		// partition and after partition 
		quickSort(arr, low, pi - 1);
		quickSort(arr, pi + 1, high);
	}
}

// Driver program to test above functions 
int arr[500000];//如果在main()内部声明,可能会因空间不足而无输出。
int main()
{
	clock_t start, end;
	srand(10086);

	
	for (int j = 0; j < 500000; j++)
			arr[j] = j;//rand() % 500000;
	int n = 500000;


	start = clock();
	quickSort(arr, 0, n - 1);
	end = clock();

	//printf("Sorted array: \n"); 
	//printArray(arr[1], n); 
	printf("time=%f\n", (double)(end - start) / (CLOCKS_PER_SEC * 3));
	return 0;
}

(二)数据记录

无结果,每次都超时而退出。验证了我们之前的猜想。

二、简单快速排序(随机排列的数列)

(一)对代码一的修改如下

int main()
{
	clock_t start, end;
	srand(10086);

	for (int j = 0; j < 500000; j++)
		arr[j] = rand() % 500000;//原来是有序的数列,现在是随机排列的
	int n = 500000;


	start = clock();
	quickSort(arr, 0, n - 1);
	end = clock();

	printf("time=%f\n", (double)(end - start) / (CLOCKS_PER_SEC));
	return 0;
}

(二)数据记录

次序耗时(s)
10.084999
20.090999
30.090000
40.089001
50.087999
平均0.088600

三、中间值均分(排列好的数列)

(一)对代码一的修改如下

int partition(int arr[], int low, int high)
{
	int pivot = arr[(low+high)/2];    // 将中心值放在已排列好的数列中间而不是开头,起到左右均分的作用
	
	int i = low;
	int j = high;
	while (i < j)
	{
		while (i < j && arr[j] > pivot)
			j--;
		if (i < j)
		{
			arr[i] = arr[j];
			i++;
		}
		while (i < j && arr[i] <= pivot)
			i++;
		if (i < j)
		{
			arr[j] = arr[i];
			j--;
		}
	}
	arr[i] = pivot;
	return i;
}

(二)数据记录

次序耗时(s)
10.086000
20.096000
30.100000
40.095000
50.100000
平均0.095400

四、中间值均分(随机排列的数列)

数据记录

次序耗时(s)
10.084000
20.079000
30.100000
40.091000
50.094000
平均0.089600

虽然相比简单快速排序法的0.088600s无明显变化,但由代码三可知该算法能胜任“最坏情况”,且时长较为稳定。看来如果我们恰当地(尽可能让中间值的大小在数列中处在中间位置)选择中间值确实可以大大减少时间开销。

五、三数取中(排列好的数列)

(一)算法简介

1.枢纽值的选取与移动

在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。
第一步操作

2.根据枢纽值进行扫描、分割

第二步操作

3.递归

重复之前的操作,直至每一部分都不需要再处理。
第三步操作(重复之前的操作)

(二)完整代码

/* C implementation QuickSort */
#include<stdio.h>
#include<stdlib.h>
#include<time.h>

/* Function to print an array */
void printArray(int arr[], int size)
{
	int i;
	for (i = 0; i < size; i++)
		printf("%d ", arr[i]);
	printf("\n");
}

void swap(int *a, int *b)
{
	int temp;
	temp = *a;
	*a = *b;
	*b = temp;
}

void swap1(int arr[], int a, int b) 
{
	int temp = arr[a];
	arr[a] = arr[b];
	arr[b] = temp;
}

void median3(int A[], int left, int right) 
{
	int center = (left + right) / 2;
	if (A[left] > A[center])
		swap(&(A[left]), &(A[center]));
	if (A[left] > A[right])
		swap(&(A[left]), &(A[right]));
	if (A[center] > A[right])
		swap(&(A[center]), &(A[right]));
	swap(&(A[center]), &(A[right - 1]));
}

/* The main function that implements QuickSort
 arr[] --> Array to be sorted,
  low  --> Starting index,
  high  --> Ending index */
void quickSort(int arr[], int low, int high)
{
	if (low < high)
	{
		/* pi is partitioning index, arr[p] is now
		   at right place */
		median3(arr, low, high);
		int pivot = high - 1;
		int i = low;
		int j = high - 1;
		while (1) 
		{
			while (arr[++i] < arr[pivot]) {}
			while (j > low && arr[--j] > arr[pivot]) {}
			if (i < j) 
			{
				swap1(arr, i, j);
			}
			else 
			{
				break;
			}
		}
		if (i < high) 
		{
			swap1(arr, i, high - 1);
		}
		quickSort(arr, low, i - 1);
		quickSort(arr, i + 1, high);
	}
}

// Driver program to test above functions 
int arr[500000];
int main()
{
	clock_t start, end;
	srand(10086);


	for (int j = 0; j < 500000; j++)
		arr[j] = j;//rand() % 500000;
	int n = 500000;


	start = clock();
	quickSort(arr, 0, n - 1);
	end = clock();

	//printf("Sorted array: \n"); 
	//printArray(arr[1], n); 
	printf("time=%f\n", (double)(end - start) / CLOCKS_PER_SEC);
	return 0;
}

(三)数据记录

次序耗时(s)
10.050000
20.046000
30.044000
40.046000
50.052000
平均0.047600

相较代码三的0.095400s有了较大的提升。尽管平均时间复杂度也为O(nlogn)级,但我们通过三数取中法选择了一个枢纽值,使得一部分记录的关键字均小于另一部分。然后分别对这两组继续进行排序,以使整个序列有序。这样可以很大程度上避免分组"一边倒"的情况。(也就是代码二)

六、三数取中(随机排列的数列)

数据记录

次序耗时(s)
10.113000
20.117000
30.109000
40.108000
50.117000
平均0.112800

三数取中法面对随机排列的数列时表现得没有中间值均分法优秀。但实际生活中大多数时候我们面对的都是基本排列好的文件,因此三数取中法更具优势。

七、三数取中+直接插入(排列好的数列)

(一)算法简介

根据我们之前学过的知识,直接插入算法在处理少量排好序的文件时具有较大的优势,而这恰恰可以与快速排序算法形成互补(快速排序算法不适于排好序的文件)。因此,我们可以在大多数元素都已经排好序的情况下使用直接插入排序法,从而加快速度。

(二)完整代码

/* C implementation QuickSort */
#include<stdio.h>
#include<stdlib.h>
#include<time.h>

/* Function to print an array */
void printArray(int arr[], int size)
{
	int i;
	for (i = 0; i < size; i++)
		printf("%d ", arr[i]);
	printf("\n");
}

void swap(int *a, int *b)
{
	int temp;
	temp = *a;
	*a = *b;
	*b = temp;
}

void swap1(int arr[], int a, int b)
{
	int temp = arr[a];
	arr[a] = arr[b];
	arr[b] = temp;
}

void median3(int A[], int left, int right)
{
	int center = (left + right) / 2;
	if (A[left] > A[center])
		swap(&(A[left]), &(A[center]));
	if (A[left] > A[right])
		swap(&(A[left]), &(A[right]));
	if (A[center] > A[right])
		swap(&(A[center]), &(A[right]));
	swap(&(A[center]), &(A[right - 1]));
}

/* The main function that implements QuickSort
 arr[] --> Array to be sorted,
  low  --> Starting index,
  high  --> Ending index */
void directSort(int arr[], int n)				//直接插入排序
{
	int i, j;
	int temp;
	for (i = 1; i < n; i++)
	{
		j = i;
		temp = arr[i];
		while (j > 0 && temp < arr[j - 1])
		{
			arr[j] = arr[j - 1];
			j--;
		}
		arr[j] = temp;
	}
}

void quickSort(int arr[], int low, int high)
{
	if (low + 20< high)								//剩余20个记录交给更快捷的直接插入排序法
	{
		/* pi is partitioning index, arr[p] is now
		   at right place */
		median3(arr, low, high);
		int pivot = high - 1;
		int i = low;
		int j = high - 1;
		while (1)
		{
			while (arr[++i] < arr[pivot]) {}
			while (j > low && arr[--j] > arr[pivot]) {}
			if (i < j)
			{
				swap1(arr, i, j);
			}
			else
			{
				break;
			}
		}
		if (i < high)
		{
			swap1(arr, i, high - 1);
		}
		quickSort(arr, low, i - 1);
		quickSort(arr, i + 1, high);
	}
}

// Driver program to test above functions 
int arr[500000];
int main()
{
	clock_t start, end;
	srand(10086);


	for (int j = 0; j < 500000; j++)
		arr[j] = j;//rand() % 500000;
	int n = 500000;


	start = clock();
	quickSort(arr, 0, n - 1);			//先使用快速排序
	directSort(arr + 250000 - 1, 41);	//再使用直接插入排序
	end = clock();

	//printf("Sorted array: \n"); 
	//printArray(arr[1], n); 
	printf("time=%f\n", (double)(end - start) / CLOCKS_PER_SEC);
	return 0;
}

(三)数据记录

次序耗时(s)
10.028000
20.021000
30.020000
40.021000
50.018000
平均0.021600

相较代码五的0.047600s有了较大的提升。

八、三数取中+直接插入(随机排列的数列)

数据记录

次序耗时(s)
10.090000
20.084000
30.096000
40.094000
50.093000
平均0.091400

相较代码六的0.112800s有了一定的提升。

数据处理

排列好的随机排列
简单快排超时0.0886
中间值均分0.09540.0896
三数取中0.04760.1128
三数取中+直接插入0.02160.0914

综上,“三数取中+直接插入”面对已排列好的数列时有着巨大优势,更适用于实际情况。简单快排更适合随机排列。

总结与反思

要拓展思维,敢于探索,在实践中不断优化算法。此外,还要结合实际情况进行充分考虑,使得算法更符合一般情况。最后,还要具有搜集、整理资料的能力,在实践中增长知识。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值