• 数据结构之-----排序算法理解与应用


    算法的稳定性:如果待排序的两个元素Ri,Rj,其对应的关键字keyi=keyj,且在排序前Ri在Rj的前面,如果排序后Ri还在Rj的前面,则称这种排序算法是稳定的,否则称排序算法是不稳定的。

    内部排序和外部排序:内部排序是指在排序期间,元素全部存放在内存中的排序。外部排序是指排序期间元素无法全部同时存放在内存中,必须在排序过程中根据要求不断地在内外存之间移动的排序。

    1.插入排序

    1)插入排序:每次将一个待排序的记录,按其关键字大小插入到前面已经排好序的子序列中,直到全部记录插入完成。

    void InsertSort(ElemType A[], int n)
    {
    	int i, j;
    	for (i = 2; i <= n; i++)   //依此将A[2]~A[n]插入到前面已排序序列
    		if (A[i].key < A[i - 1].key) //若A[i]的关键码小于其前驱,须将A[i]插入有序表
    		{
    			A[0] = A[i];      //复制为哨兵
    			for (j = i - 1; A[0].key < A[j].key; --j)  //从后往前查找待插入位置
    				A[j+1]=A[j];   //向后拖动
    			A[j+1]=A[0];    //复制到插入位置
    		}
    }

    时间复杂度:O(n2),空间复杂度:O(1).稳定性:稳定的排序方法。

    2)希尔排序:将待排序表分割成若个形如L[i,i+d,i+2d,i+3d,.....i+kd]的特殊子表,分别进行直接插入排序。当整个表呈基本有序时,在对全体记录进行一次直接插入排序。

    过程:先去一个小于n的步长d1,把表中全部记录分成d1个组,所有距离为d1的倍数的记录放在同一组中,在各组中进行直接插入排序。然后取第二个步长d2<d1.重复上述过程,直到di=1,即所有记录在同一组中,再进行直接插入排序。

    增量求法:目前不统一,一般采用d1=n/2,,​最后一个增量为1.

    void ShellSort(ElemType A[], int n)
    {
    //对顺序表作希尔插入排序,和插入排序算法相比,做了以下修改:前后记录位置增量是dk,不是1.A[0]只是暂存单元,不是哨兵,j<0时,插入位置已达
    	for(dk=len/2;dk>=1;dk=dk/2)   //步长变化
    		for(i=dk+1;i<=n;i++)
    			if (A[i].key < A[i - dk].key)   //需将A[i]插入到有序增量子表
    			{
    				A[0]=A[i];   //暂存在A[0]
    				for (j = i - dk; j > 0 && A[0].key < A[j].key; j -= dk)
    					A[j+dk]=A[j];
    				A[j + dk] = A[0];
    				
    			}
    }

    时间复杂度:当n在某个特定范围时为,最坏情况下为:,空间复杂度O(1)

    不稳定排序

    2.交换排序

    冒泡排序:将设待排序表长为n,从后往前两两比较相邻元素的值,若为逆序,则交换他们,直到序列比较完。此为一趟冒泡。结果为将最小的元素交换到待排序的第一个位置。下一趟冒泡时,前一趟确定的最小元素不再参与比较,待排序列减少一个元素,每趟排序吧最小元素放到最终位置,这样最多做n-1趟冒泡就把所有元素排好。

    void BobbleSort(ElemType A[], int n)
    {
    //用冒泡排序法将序列A中的元素按从小到达排列
    	for (i = 0; i < n - 1; i++)
    	{
    		flag = false;  //表示本趟冒泡是否发生交换的标志
    		for (j = n - 1; j > i; j--)  //一趟冒泡过程
    		
    			if (A[j - 1].key > A[j].key)//若为逆序
    			{
    				swap(A[j-1],[j]);   //交换
    				flag = true;
    			}
    			if (flag == false)
    				return;
    	}
    }
    1. 快速排序:快速排序是对冒泡排序的一种改进。其基本思想是基于分治法的:在待排序L[1....n]中任意取一个元素pivot作为基准,通过一趟排序将待排序表划分为独立的两部分L[1...k-1],L[k+1....n]使得L[1....k-1]中所有元素小于等于pivot,L[k+1...n]中所有元素大于pivot,则pivot则放置在最终位置上L(k),这个过程称为一趟快速排序。而后分别递归的对两个子表重复上述过程,直到每一部分内只有一个元素或空为止(所有元素放置在最终位置上)

    过程:首先假定划分算法已知,记为partition(),返回上述中的k,L(k)已经在最终位置上,所以可以先对表进行划分,而后对表调用同样的排序操作。递归的调用快速排序算法进行排序。程序结构如下:

    void QuickSort(ElemType A[], int low, int high)
    {
    	if (low < high)   //边界条件,即递归跳出的条件
    	{//partition() 就是划分操作,将表A[low...high]划分为满足上述条件的两个子表
    		int pivotpos = partition(A,low,high); //划分
    		QuickSort(A,low,pivotpos-1);  //依此对两个子表进行递归排序
    		QuickSort(A,pivotpos+1,high);
    	}
    }
    //不难看出,快速排序的关键在于划分操作,性能取决于划分操作的好坏
    //快速排序分治partition有两种方法

    1)两个下标分别从首,尾向中间扫描的方法

    假设每次都是以当前表中第一个元素作为枢纽值对表进行划分,则必须将表中比枢纽值大的元素向右移动,比枢纽值小的元素向左移动,使得一趟partition()操作后,表的元素被枢纽值一分为二。

    int partition(elemtype A[], int low, int high)
    {
    	elemtype pivot = A[low];     //将当前表中第一个元素设为枢纽值,对表进行划分
    	wihle(low < high)     //循环跳出条件
    	{ 
    		while (low < high&&A[high] >= pivot) --high;
    		A[low] = A[high];                //将比枢纽值小的元素移动到左端
    		while (low < high&&A[low <= pivot) low++;
    		A[high]=A[low];       //将比枢纽值大的元素移动到右端
    	}
    	A[low] = pivot;  //枢纽元素存放到最终位置
    	return low;     //返回存放枢纽的最终位置
    }

    若初始序列3,8,7,1,2,5,6,4排序过程如下:

    2 8 7 1 2 5 6 4

    2 8 7 1 8 5 6 4

    2 1 7 1 8 5 6 4

    2 1 7 7 8 5 6 4

    2 1 3 7 8 5 6 4    //A[high]A[low]

    2)两个指针索引一前一后逐步向后扫描

    int partition(elemtype A[], int p, int r)
    {
    	elemtype x = A[r];  //以最后一个元素,A[r]为主元
    	int i = p - 1;
    	for (int j = p; j <= r - 1; ++j)
    	{
    		if (A[j] <= x)
    		{
    			++i;
    			exchange(A[i],A[j]);
    		}
    	}
    	exchange(A[i+1],A[r]);
    	return i + 1;
    }//若初始化序列3 8 7 1 2 5 6 4则排序的大致如下:
    3 8 7 1 2 5 6 4  //3与3交换,不移动元素,比较一次
    3 1 7 8 2 5 6 4   //8与1交换,交换依此,比较三次
    3 1 2 8 7 5 6 4   //7与2交换,交换一次,比较一次
    3 1 2 4 7 5 6 8   //8与4交换,交换一次,比较两次

    快速排序是所有内部排序算法中平均性能最优的排序算法。在快速排序算法中,并不产生有序子序列,但每一趟排序后将一个元素(基准元素)放在其最终位置上。当初始排序表基本有序或基本逆序是,就得到最坏情况下的时间复杂度O(n2).

    A快排一次排序的应用

    A)区分数组中大小写字母(编写函数,让小写字母在所有大写字母之前)

    bool isUpper(char a)
    {
    	if (a >= 'A'&&a <= 'Z')
    		return true;
    	return false;
    }
    
    bool isLower(char a)
    {
    	if (a >= 'a'&&a <= 'z')
    		return true;
    	return false;
    }
    
    void partition(char A[], int low, int high)  //开排一次排序第一种策略的另外一种实现
    {
    	while (low < high)
    	{
    		while(low < high&&isUpper(A[high]))high--;
    		while (low < high&&islower(A[low]))low++;
    		char temp = A[high];
    		A[high]=A[low];
    		A[low] = temp;
    	}
    
    }
    void main()
    {
    	char a[7] = {'a','A','Z','d','B','s','b'};
    	partition(a,0,6);
    }

    b)给定含n个元素的整型数组a,包含0和非0,对数组进行排序,使排序后满足1.排序后的所有0元素在前,非零元素在后,且非零元素排序前后相对位置不变,不能使用额外的存储空间。

    void partition(int A[], int p, int r)
    {
    	int i = r + 1;
    	for (int j = r; j >= p; j--) //从后往前遍历,也可从前往后遍历
    	{
    		if (A[j] != 0)
    		{
    			--i;
    			int temp = A[i];
    			A[i]=A[j];
    			A[j] = temp;
    		}
    	}
    }
    void main()
    {
    	int a[7] = {0,3,0,2,1,0,0};
    	partition(a,0,6);
    }

    c)荷兰国旗问题

    while (current <= end)
    {
    	if (array[current] == 0)
    	{
    		swap(array[current],array[begin]);
    		current++;
    		begin++;
    	}
    	else if (array[current == 1])
    	{
    		current++;
    	}
    	else
    	{
    		//array[current]==2
    		swap(array[current], array[end]);
    		end--;
    	}
    }

    D)输入n个整数,输出其中最小的k个。

    思路1:将输入的n个数排序,这样排在最前面的k个数就是最小的k个数。

    思路2:假设最小的k个数中最大的为A。在快排中,先在数组中随机选择一个数字,然后调整数组中数字的顺序,使得比选中数字小的数字排在他的左边,比选中数字大的排在他的右边(快排一次)

    若选中的数字下表刚好是k-1(从0开始),那么这个数字(A)加上左侧的k-1个数就是最小的k个数。如果他的小标大于k-1,则A位于他的左侧,我们可以在他的左边部分的数组中查找。若小标小于k-1,那么A应该位于他的右边,我们可以接着在他的右边部分中寻找。(发现这是一个递归问题,但是我们找到的k个数不一定是有序的)

    //input是输入数组,元素个数为n,output用来保存最小的k个数的数组。
    void getLeastKNum(int* input, int n, int* output, int k)
    {
    	if (input == NULL || output == NULL || k > n || n <= 0 || k <= 0)
    		return;
    	int start = 0;
    	int end = n - 1;
    	int index = partition(intput, start, end);//一次划分函数见前
    	while (index != k - 1)
    	{
    		if (index > k - 1)
    		{
    			end = index - 1;
    			index = partition(input, start, end);
    		}
    		else
    		{
    			start = index + 1;
    			index = partition(input, start, end);
    		}
    	}
    	for (int i = 0; i < k; i++)
    		output[i] = input[i];
    }     //该算法的平均时间复杂度为O(n)

    3.选择排序

    思想:每一趟在后面n-i+1(i=1,2..n-1)个待排序元素中选取关键字最小的元素,作为有序子序列的第i个元素,直到n-1趟做完,待排序元素只剩下1就不用再选了。

    1)简单选择排序

    void SelectSort(elemtype A[], int n)
    {//对表A作简单选择排序,A[]从0开始存放元素
    	for(i=0;i<n-1;i++)  //总共进行n-1趟排序
    	{
    	min=i;     //记录最小元素位置
    	for(j=i+1;j<n;j++)
    		if (A[j]<A[i])  //在A[i..n-1]中选择最小元素
    			min=j;    //更新最小元素位置
    		if(min!=i)swap(A[i],A[min]); //与第i个位置交换
    	}
    }

    空间复杂度:O(1)。时间复杂度:元素移动较少不超过3(n-1)(一次swap三次元素移动)。最好移动0次(此时表已经有序)。但是元素间比较的次数与序列的初始状态无关,始终为n(n-1)/2次。时间复杂度为O(n2).

    2)堆排序

    堆排序是一种树形选择排序方法,在排序过程中将L[1..n]视为一棵完全二叉树的顺序村粗结构。利用完全二叉树中双亲结点和孩子结点之间的内在关系,在当前无序区中选择关键字最大(或最小)的元素。

    堆排序的实质是构建初始堆,对初始序列建堆,就是一个反复筛选的过程。

    A)根据初始关键字序列(20,18,22,16,30,19)构建初始大根堆。

    void BuildMaxHeap(elemtype A[], int len)
    {
    	for (int i = len / 2; i > 0; i--)
    		AdjustDown(A,i,len);
    }
    void AdjustDown(elemtype A[], int k, int len)
    {//adjustDown将元素k向下进行调整,堆主要的两个函数之一,另一个adustun
    	A[0]=A[k];    //A[0] 暂存
    	for (i = 2 * k; i <= len; i = i * 2)  //沿k较大的子节点向下筛选
    	{
    		if(i < len&&A[i] < A[i + 1])
    			i++;     //取key较大的子节点的下标
    		if (A[0] >= A[i]) break;  //筛选结束
    		else
    		{
    			A[k]=A[i];    //将A【i】调到双亲结点
    			k = i;    //修改k值,继续向下筛选
    		}
    	}
    	A[k] = A[0];   //被筛选结点的值放入最终位置
    }

    在元素个数为n的序列上建堆,其时间复杂度为O(n),这说明可以在线性时间内,将一个无序数组建成一个大顶堆。

    B)堆排序的思想

    由于堆本身的特点(以大顶堆为例),堆顶元素就是最大值。输出堆顶元素后,通常将堆底元素放入堆顶,此时根节点已不满足堆的性质,将堆顶元素向下调整继续保持大顶堆性质,输出堆顶元素,重复,直到仅剩一个元素为止。

    void HeapSort(elemtype A[], int len)
    {
    	BuildMaxHeap(A,len);   //初始建堆
    	for (i = len; i > 1; i--)   //n-1趟交换和建堆过程
    	{
    		swap(A[i],A[1]);  //输出堆顶元素(和堆底元素交换)
    		adjustDown(A,1,i-1);  //整理,把剩余的i-1个元素整理成堆
    	}
    }

    C)堆的插入和删除

    删除堆顶元素时,先将堆的最后一个元素与堆顶元素交换,有序性质破坏,需要堆根结点进行向下调整。

    对堆进行插入操作时,先将新结点放在堆的末端,再对这个新结点执行向上调整操作,大顶堆插入操作如下图所示:

    向上调整算法如下所示:

    D)堆排序的应用(最小k个数)

    输入n个整数,输出其中最小的k个.(用堆排序来解决,适合处理海量数据)

    思路:首先读入k个数创建一个大小为k的大顶堆,然后依此读入剩余数据,如果当前数据比大顶堆的堆顶小,则用这个数代替当前堆顶元素,并调整时期保持大顶堆性质,如果当前数据比堆顶大,则此数不可能为最小的k个整数之一,故抛弃此数。(时间复杂度:O(nlogk))

    int a[n]; //数组a中存放输入的n个数
    int b[k + 1];//从a中依此读入k个数a[0].....a[k-1]第一个数存在b[1]中
    BuildMaxHeap(b,k);//调整b为大顶堆
    for (int i = k; i < n; i++)
    {
    	if (a[i] > a[1])
    		continue;
    	else
    	{
    		b[1] = a[i];
    		adjustdown(b,1,k);
    	}
    }   //当需要求最大的k个数时,只需将大顶堆换位小顶堆。

    4.归并排序

    1. 二路归并排序(内部排序,基于分治算法的,使用辅助空间)

    含义:将两个或两个以上的有序表组合成一个新的有序表。假定待排序表含有n个记录,则可视为n个有序子表,每个子表长度为1,两两归并,得到​长度为2的有序表,再两两归并...如此重复,直到合成一个长度为n的有序表为止。

    过程:分解:将n个元素的待排序表分成各含n/2个元素的子表,采用二路归并算法对两个子表递归的进行排序。

    合并:合并两个已排序的子表得到排序结果。

    void MergeSort(elemtype A[], int low, int high)
    {
    	if (low < high)
    	{
    		int mid = (low + high) / 2;  //对中间划分两个子序列
    		mergeSort(A,low,mid);   //对左侧子序列进行递归排序
    		mergeSort(A,mid+1,high); //对右侧子序列进行递归排序
    		merge(A, low, mid, high);
    	}
    }

    Merge()的功能时将前后相邻的两个有序表归并为一个有序表的算法。设两段有序表A【low...mid】A[mid+1...high]存放在同一顺序表中相邻的位置上,先将他们复制到辅助数组B中,每次从对应B中的两个段取出一个记录进行关键字比较,将较小者放入A中,当输入B中有一段超出其表长,则将另一段剩余部分直接复制到A中。

    elemtype *B = (elemtype *)malloc(n + 1) * sizeof(elemtype));  //辅助数组B
    	void Merge(elemtype A[], int low, int mid, int high)
    	{
    	//表A的两段A[low...mid]和A[mid+1...high]各自有序,将他们合并成一个有序表
    		for (int k = low; k <= high; k++)
    			B[k]=A[k];  //加A中所有元素复制到B中
    		for (int i=low, j = mid + 1, k = i; i <= mid&&j <= high; k++)
    		{
    			if (B[i] < B[j])     //比较B的左右两段元素
    				A[k] = B[i++];    //将较小的值复制到A中
    			else
    				A[k]=B[j++];
    		}
    		while (i <= mid) A[k++]=B[i++];   //若第一个表未检测完,复制
    		while (j <= high) A[k++] = B[j++];  //若第二表未检测完,复制
    	}//最后两个while循环中只有一个会执行

     A)合并两个排好序的链表(连个递增排序链表,合并他们使新链表结点仍然是按照递增排序的)

    struct LIstNode 
    	{
    		int value;
    		ListNode *pNext;
    	};      //原理:二路归并排序的merge函数,递归代码如下:
    	ListNode* mergeList(ListNode* list1, ListNode* list2)
    	{
    		if (list1 == NULL)
    			return list2;
    		else if (list2 == NULL)
    			return list1;
    		ListNode* pHead = NULL;
    		if (list1->value < list2->value)
    		{
    			pHead = list1;
    			phead->pNext = mergeList(list1->pNext, list2);
    		}
    		else
    		{
    			pHead = list2;
    			phead->pHead = mergeLIst(list1,list2->pNext);
    		}
    		return pHead;
    	}

    b)给定有序数组a,b.已知数组a末尾有足够空间容纳b,请实现将b合并到a中。函数头如下:

    Void merge(int a[],int b[],int n,int m)//n为数组a的元素个数,m为数组b的元素个数

    思路:先计算总元素个数,从数组末尾(最大元素)开始归并。

    void merge(int a[], int b[], int n, int m)
    	{
    		int k = m + n - 1;
    			int i = n - 1;
    		int j = m - 1;
    		while (i> = 0 && j >= 0)
    		{
    			if (a[i] > b[j])
    			{
    				a[k--] = a[i--];
    			}
    			else
    			{
    				a[k--] = a[j--];
    			}
    		}
    		while (j >= 0)
    		{
    			a[k--]=b[j--];
    		}
    	}

    C)原地归并排序(二叉归并排序 内部排序,不适用辅助空间)

    原地归并排序不需要辅助数组即可归并。关键在merge这个函数。假设有两段递增的子数组arr[begin....mid-1]和arr[mid...end],但整个数组不是递增的。其中i=begin,j=mid,k=end.

    然后把i到mid-1的部分和mid到j-1的部分对调(可通过三次逆序实现)较小部分就调到前面去了,此时数组变为0 1 2 3 4 5 6 9 7 8(前面有序了,后面又是两个递增子数组,继续迭代即可)

    void reverse(int *arr, int n)
    	{  //将长度为n的数组逆序
    		int i = 0, j = n - 1;
    		while (i < j)
    		{
    			swap(arr[i],arr[j]);//将两个实参图解交换
    			i++;
    			j--;
    		}
    	}
    	void exchange(int *arr, int n, int i) //将含有n个元素的数组向左循环移位i个位置
    	{
    		reverse(arr,i);
    		reverse(arr+i,n-i);
    		reverse(arr, n);
    	}
    	//数组两个有序部分合并,本节图解的实现
    	void merge(int *arr,int begin,int mid,int end)
    	{
    		int i = begin, j = mid, k = end;
    		while (i < j&&j <= k)
    		{
    			int step = 0;
    			while (i < j&&arr[i] < arr[j])
    				i++;
    			while (j < k&&arr[j] <= arr[i])
    				j++;
    			step++;
    		}
    		//arr+i为子数组首地址,j-i为子数组元素个数,j-i-step为左循环移位个数
    		exchange(arr+i,j-i,j-i-step);
    		i = i + step;
    	}
    	void MergeSort(int *arr, int l, int r)
    	{
    		if (l < r)
    		{
    			int mid = (l + r) / 2;
    			MergeSort(arr,l,mid);
    			MergeSort(arr,mid+1,r);
    			merge(arr,l,mid+1,r);
    		}
    	}
    	void main()
    	{
    		int arr[] = {6,4,3,1,7,8,2,9,5,0};
    		int len = sizeof(arr) / sizeof(arr[0]);
    		MergeSort(arr,0,len-1);
    	}

    D)多路归并排序(外部排序)

    外部排序是指大文件的排序,即待排序的记录存储在外部存储器上,待排序的文件无法一次装入内存,需要在内存和外部存储器之间进行多次数据交换,以达到排序整个文件的目的。

    思路:外部排序最常用的算法是多路归并排序,即将源文件分解成多个能够一次性装入内存的部分,分别把每一部分调入内存完成排序,然后对已排序的子文件进行归并排序。

    从二路到多路,增大k可以减少外存信息读写时间,但k个归并段中选择最小的记录需要比较k-1次,为了降低选出每个记录需要的比较次数k,引入败者数

    败者树可视为一棵完全二叉树,每个叶结点存放各归并段在归并过程中当前参加比较的记录,内部结点用来记忆左右子树中的失败者,让胜者网上继续进行比较,一直到根节点。如果比较两个数,大的为失败者,小的为胜利者,则根节点指向的数为最小数。

    图中第一个叶子结点为b0.k路归并的败者树深度为​,因此k个记录中选择最小关键字,最多需要​次比较,比依此比较的k-1次小得多。

    案例:有20个有序数组,每个数组有500个unsigned int元素,降序排序。要求从这10000个元素中选出最大的500ge.

    思路:依此从20个有序数组中选择一个当前元素,两两比较,然后找出最大的数,循环500次,即可选择出500个最大的数。但是这里每选择一个最大元素,需要比较19次,效率低。

    改进方法1:利用堆,从20个数组中各取一个数,并记录每个数的来源数组,建立一个含有20个元素的大顶堆。此时堆顶就是最大元素,去除堆顶元素,并从堆顶元素的来源数组中取下一个元素加入堆,调整堆后再取最大值,一直这样进行500次即可。时间复杂度,其中n为要选出的元素个数,k为有序数组个数。

    改进方法2:利用败者树。从20个数组中各取一个数,并记录每个数的来源数组,建立一个20路归并的败者树。此时败者树输出的就是最大的数,然后从最大数的来源数组继续取下一个数加入败者树,继续比较,直到输出500个数为止。时间复杂度为其中n为要选出的元素个数,k为有序数组个数。

    const int branchesLength = 20;//共有20路数组
    	//20个一维数组,每个数组有500个元素,为叶子结点提供数据。本题只给出测试用的40个元素,输出最大的前10个元素。
    	int branches[branchesLength][500] = {
    		{1000,900} ,{999,888} ,{1001,990} ,{887,877} ,{987,978},
    		{1001,901} ,{992,883}, {1005,992}, {887,877}, {987,978},
    		{1002,902} ,{993,884}, {1007,991}, {887,877}, {987,978},
    		{1003,903} ,{994,882} ,{989,900} ,{887,877} ,{987,978}
    	};
    	//败者树的非叶子结点,记录数据源的索引位置,根据结点的值可以定位到所指向的数据源。
    	int tree[branchesLength]; //败者树的叶子结点,叶子结点和数据源是一一对应的,即第一个叶子结点记录第一个数据源的当前数据,第一个叶子结点为b0
    	int nodes[branchesLength];  
    	int nodes_iterator[branchesLength] = {0};//nodes_iterator[i]记录第i路数组当前已取得第几个元素
    	void put(int index)//设置第index叶结点的下一个数据
    	{
    		nodes[index] = branches[index][nodes_iterator[index]++];
    	}
    	int get(int index)//获取第index个叶子结点的当前数据
    	{
    		return nodes[index];
    	}
    	//调整第index个叶子结点,具体调整为:叶子结点和父节点比较,败者留在父结点位置,胜者继续和父节点的父节点,兄弟节点比较,直到整个树的根节点。
    	void adjust(int index)  //此函数为主要函数
    	{
    		int size = branchesLength;
    		int t = (size + index) / 2;  //计算父节点
    		while (t > 0)
    		{
    			if (get(tree[t]) > get(index))
    			{//败者留在父节点位置
    				int temp = tree[t];
    				tree[t] = index;
    				index = temp;
    			}
    			t /= 2;
    		}
    		tree[0] = index;
    	}
    	vector<int>merge()  //依此读取数据源的数据进行归并排序,返回排序后的数据列表
    	{
    		vector<int> list1;  //记录排好序的数据
    		int top;
    		int i = 0;
    		while (i < 10)
    		{//仅输出10个数据供测试
    			top = tree[0];
    			list1.push_back(top);
    			i++;
    			put(tree[0]);
    			adjust(tree[0]);
    		}
    		return list1;
    	}
    	void init()  //初始化构建败者树
    	{
    		int size = branchesLength;
    		for (int i = 0; i < size; i++)  //为叶子节点赋值
    			put(i);
    			int winner = 0;
    			for (int i = 1; i < size; i++)
    			{
    				if (get(i) < get(winner))
    				{
    					winner = i;
    				}
    			}
    			for (int i = 0; i < branchesLength; i++)  //非叶子节点初始化为冠军节点
    				tree[i] = winner;
    			for (int i = size - 1; i >= 0; i--)  //从后向前依此调整非叶子节点
    				adjust(i);
    	}
    	void main()
    	{
    		init();
    		merge();
    	}

     不同排序算法的比较

    总结:

    1.比较次数和初始排列无关的是选择排序。

    2.在初始序列基本有序的情况下,最优的是插入排序,此时插入排序时间复杂度为O(n),其次是冒泡排序,时间复杂度也为O(n).快速排序此时性能最差,时间复杂度为,同时快速排序在厨师序列逆序的时候,性能也最差,时间复杂度也为

    3.堆排序对初始数据集的排列顺序不敏感,在最好,最坏和平均情况下,堆排序的时间复杂度为

    4.节俭排序,一对数字不进行两次或两次以上的比较。包括(插入排序,归并排序)

    5.基于比较的排序算法时间复杂度的下界(最好的时间复杂度)为:

  • 相关阅读:
    面试技巧
    [CODEVS1116]四色问题
    [CODEVS1216]跳马问题
    [CODEVS1295]N皇后(位运算+搜索)
    [CODEVS1037]取数游戏
    [CODEVS1048]石子归并
    [NOIP2012]同余方程
    C++深入理解虚函数
    Attention Model
    faster-rcnn系列原理介绍及概念讲解
  • 原文地址:https://www.cnblogs.com/mydomain/p/11250876.html
Copyright © 2020-2023  润新知