算法导论(第9章-中位数和顺序统计学)最大值和最小值

n个数中同时找出最大值跟最小值:

例子:n = 7

5  1  2  3  6  4  8

方法一:独立地找出最大值和最小值,各用n-1次比较,共有2n-2次比较。

方法二:成对地处理元素,先将一对输入元素相互比较,然后把较小者与当前最小值比较,把较大者与当前最大值比较,因此每两个元素需要比较3次。(n为奇数时,将最大值和最小值都设为第一个元素的值,然后成对地处理余下的元素,总共做了3*[n/2]次比较,[]为下界。n为偶数时,就对前两个元素做一次比较,以决定最大值和最小值的初始值,然后成对地处理余下的元素,总共做了3*(n-2)/2 + 2 = 3*n/2-2次比较,不管是哪一种情况,总的比较次数至多是3[n/2],[]为下界)

/* ---------------------------------------------------------------------------------------------------------------------------------*/

证明:在最坏情况下,利用n + [lg[n]] - 2 次比较,即可找到n个元素中的第二小元素。(提示:同时找最小元素)

常规查找:先找出最小值,用了n-1次比较,再找次小值,用了n-2次比较。一共用了2n-3次比较,无法满足题目要求。

我们可以利用树的结构来完成查找:成对比较,不断取小的值,这样可以求出最小值。而在比较的过程可以生成一棵树。具体如图

例子:n = 7

5  1  2  3  6  4  8

找出最小值(即根节点)需要比较的次数为n-1(其实是非叶子节点的个数),然后从根节点往叶子沿着某条路径开始寻找次小值(某条路径是指孩子节点的值 = 当前节点的值 的那条路径)。因为在生成树的时候次小值一定会跟最小值比较过,所以在沿着路径走的过程中,需要比较的元素是 孩子节点值 != 根节点值 的那个节点的值,即图中红色标记的结点,个数最多为lg(n)-1个,即树的高度 -
1(减去的是根节点,不需要比较)。找出红色结点的最小值即为最终的次小值。所以总的次数达到n + [lg(n)] - 2次。

遗憾的是~

经过测试,普通查找(比较次数 = 2n-3)比利用二叉树查找(比较次数 = n + [lg(n)] - 2)快上10多倍,可能是后者操作复杂引起的时间消耗。不过后者确实是个很好的思路!

贴上代码:

/* 产生测试数据 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

int main()
{
	freopen("in.txt", "w", stdout);
	srand(unsigned(time(NULL)));
	int n = 8000000;
	int i;
	printf("%d\n", n);
	for(i = 0; i < n; i++){
		printf("%d\n", rand()%n + 10);
	}
	return 0;
}
/* 包括最坏情况以 n + lg(n) - 2 的比较次数寻找数组中的次小值*/
/* 运行环境:windows平台 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <windows.h>
#define N 100010000
#define INF (1<<30)
#define MIN(x, y) ((x) < (y) ? (x) : (y))

typedef struct TREE
{
	int data;
	TREE *lchild, *rchild;
}Tree;

Tree *t[N];
int a[N];

bool Create(Tree *&T)
{
	T = NULL;
	T = (Tree *)malloc(sizeof(Tree));
	if(NULL == T){
		printf("Error! overflow!\n");
		return 0;
	}
	return 1;
}

bool Init(Tree *t[], int n)
{
	int i;
	Tree *T;
	for(i = 0; i < n; i++){
		if(!Create(T)) return 0;
		scanf("%d", &T->data);
		T->lchild = T->rchild = NULL;
		t[i] = T;
	}
	return 1;
}

void ShowInit(Tree *t[], int n)
{
	int i;
	for(i = 0; i < n; i++)
		printf("%d ", t[i]->data);
	printf("\n");
}

bool CreateTree(Tree *t[], int n)
{
	int len = n;
	int u, i;
	Tree *T;
	while(len > 1)
	{
		u = 0;
		for(i = 0; i + 1 < len; i += 2){
			if(!Create(T)) return 0;
			T->data = MIN(t[i]->data, t[i+1]->data);
			T->lchild = t[i];
			T->rchild = t[i+1];
			t[u++] = T;
		}
		if(len & 1) t[u++] = t[len-1];
		len = u;
	}
	return 1;
}

void ShowTree(Tree *T)
{
	if(NULL == T) return;
	printf("%d ", T->data);
	ShowTree(T->lchild);
	ShowTree(T->rchild);
}

void FindSecMin(Tree *T, const int base, int &ans)
{
	if(NULL == T->lchild && NULL == T->rchild) return;
	if(base == T->lchild->data){
		ans = MIN(ans, T->rchild->data);
		FindSecMin(T->lchild, base, ans);
	}
	else{
		ans = MIN(ans, T->lchild->data);
		FindSecMin(T->rchild, base, ans);
	}
}

int main()
{
	freopen("in.txt", "r", stdin);
	int i;
	Tree *T;
	int n, min2;
	while(~scanf("%d", &n))
	{
#if 0
		for(i = 0; i < n; i++)
			scanf("%d", &a[i]);
		int flag = 0;
		int min1 = INF;

	/* --------------------------------------------------- */
     	FILETIME beg,end;//<windows.h>里的计时器
		GetSystemTimeAsFileTime(&beg);
	/* --------------------------------------------------- */
		for(i = 0; i < n; i++){
			if(a[i] < min1){
				min1 = a[i];
				flag = i;
			}
		}
		min2 = INF;
		for(i = 0; i < n; i++){
			if(a[i] < min2 && i != flag)
				min2 = a[i];
		}
	/* --------------------------------------------------- */
		GetSystemTimeAsFileTime(&end);
		long time = 100*(end.dwLowDateTime-beg.dwLowDateTime);
	/* --------------------------------------------------- */

		printf("%d\n", time);
		if(INF == min2) printf("Error!\n"); //只有一个数据
		else printf("min2 = %d\n", min2);
#endif

#if 1
		if(!Init(t, n)) return 1;
		//ShowInit(t, n); //显示初始数据

	/* --------------------------------------------------- */
		FILETIME beg,end;//<windows.h>里的计时器
		GetSystemTimeAsFileTime(&beg);
	/* --------------------------------------------------- */
		CreateTree(t, n); //创建树
		min2 = INF;
		FindSecMin(t[0], t[0]->data, min2); //寻找第二小的数值
	/* --------------------------------------------------- */
		GetSystemTimeAsFileTime(&end);
		long time = 100*(end.dwLowDateTime-beg.dwLowDateTime);
	/* --------------------------------------------------- */

		printf("%d\n", time);
		if(INF == min2) printf("Error!\n"); //只有一个数据
		else printf("min2 = %d\n", min2);
#endif
	}
	return 0;
}
时间: 2024-10-14 09:02:10

算法导论(第9章-中位数和顺序统计学)最大值和最小值的相关文章

算法导论 第9章 中位数和顺序统计学

/* * 算法导论 第九章 中位数和顺序统计学 * 线性时间选择元素 */ #include <iostream> #include <ctime> using namespace std; int minimum(int *arr, int len); int randomizedSelect(int *arr, int p, int r, int i); int randomizedPartition(int *arr, int p, int r); void exchange

算法导论第9章中位数和顺序统计学

#include <iostream> #include <stdint.h> #ifdef __linux #include <stdio.h> #endif // MINIMUM(A) // MIN = A[1] // for i = 2 to A.length // if min > A[i] // min = A[i] // return min int64_t minimum(int64_t* A, int64_t n) { int64_t min =

算法导论 第6章 堆排序

堆数据结构实际上是一种数组对象,是以数组的形式存储的,但是它可以被视为一颗完全二叉树,因此又叫二叉堆.堆分为以下两种类型: 大顶堆:父结点的值不小于其子结点的值,堆顶元素最大 小顶堆:父结点的值不大于其子结点的值,堆顶元素最小 堆排序的时间复杂度跟合并排序一样,都是O(nlgn),但是合并排序不是原地排序(原地排序:在排序过程中,只有常数个元素是保存在数组以外的空间),合并排序的所有元素都被拷贝到另外的数组空间中去,而堆排序是一个原地排序算法. 1.在堆排序中,我们通常使用大顶堆来实现,由于堆在

算法导论 第6章 堆排序(简单选择排序、堆排序)

堆数据结构实际上是一种数组对象,是以数组的形式存储的,可是它能够被视为一颗全然二叉树,因此又叫二叉堆.堆分为下面两种类型: 大顶堆:父结点的值不小于其子结点的值,堆顶元素最大 小顶堆:父结点的值不大于其子结点的值,堆顶元素最小 堆排序的时间复杂度跟合并排序一样,都是O(nlgn),可是合并排序不是原地排序(原地排序:在排序过程中,仅仅有常数个元素是保存在数组以外的空间),合并排序的全部元素都被复制到另外的数组空间中去,而堆排序是一个原地排序算法. 1.在堆排序中,我们通常使用大顶堆来实现,因为堆

算法导论 第8章 线性时间排序

合并排序和堆排序的时间复杂度为O(nlgn),插入排序和冒泡排序的时间复杂度为O(n^2),快速排序的时间复杂度在平均情况下是O(nlgn),这些排序算法都是通过对元素进行相互比较从而确定顺序的,因此都叫比较排序. 比较排序可以看做是决策树(一个满二叉树),因为每一次比较都是一个分支.n个元素的序列,其排序的结果有 n! 种可能(n个元素的全排),所以这个决策树有 n! 个叶子结点,假设树的高度为h,则有:n! <= 2^h,所以h >= lg(n!) = Ω(nlgn).一次比较排序就是从决

算法导论 第7章 高速排序

高速排序在最坏情况下的时间复杂度为O(n^2),尽管在最坏情况下执行时间比較差,可是高速排序一般是用于排序的最佳选择.由于其平均性能相当好,期望的执行时间为O(nlgn),且在O(nlgn)的记号中隐含的常数因子非常小. 高速排序和合并排序有相似之处,都是须要划分序列,在合并排序中.划分的过程非常easy.直接选择元素序列的中间位划分位置,排序是在合并的过程中实现的,所以合并排序的合并过程非常重要.相比合并排序,高速排序就没有合并的过程.仅仅有划分,高速排序的划分过程非常重要,排序是在划分的过程

算法导论 第13章 红黑树

二叉查找树的基本操作包括搜索.插入.删除.取最大和最小值等都能够在O(h)时间复杂度内实现,因此能在期望时间O(lgn)下实现,但是二叉查找树的平衡性在这些操作中并没有得到维护,因此其高度可能会变得很高,当其高度较高时,而二叉查找树的性能就未必比链表好了,所以二叉查找树的集合操作是期望时间O(lgn),最坏情况下为O(n). 红黑树也是一种二叉查找树,它拥有二叉查找树的性质,同时红黑树还有其它一些特殊性质,这使得红黑树的动态集合基本操作在最坏情况下也为O(lgn),红黑树通过给节点增加颜色和其它

算法导论第7章___快速排序

快速排序本质上是插入排序,但是它在这个基础上增强了算法. 下面我们来分析一下快速排序: 有了前面的分析基础,我们在来看排序算法也就容易多了. public class Quick_Sort { private void quick_Sort(int []A,int left,int right){ if(left<right){ //划区比较,这个partition 第一次!得到的就是我们刚才说的2. int partition=partition(A, left, right); //实现第一

算法导论 第7章 快速排序

快速排序在最坏情况下的时间复杂度为O(n^2),虽然在最坏情况下运行时间比较差,但是快速排序通常是用于排序的最佳选择,因为其平均性能相当好,期望的运行时间为O(nlgn),且在O(nlgn)的记号中隐含的常数因子很小. 快速排序和合并排序有相似之处,都是需要划分序列,在合并排序中,划分的过程很简单,直接选择元素序列的中间位划分位置,排序是在合并的过程中实现的,所以合并排序的合并过程很重要:相比合并排序,快速排序就没有合并的过程,只有划分,快速排序的划分过程很重要,排序是在划分的过程中实现的. /