1、 插入排序,时间复杂度O(n^2)
一个数组A[0,...,n-1],将A[j]插入到子数组A[0,...,j-1]中,其中数组A[0,...,j-1]是排好序的(假如为非降序),把A[j]和A[j-1]->A[0]逐个进行比较,大的数往后移位,将A[j]插入适当的位置(找到最后一个比它大的,放在前面)。
递归法,为排序A[0,...,n-1],先排序A[0,...,n-2],然后把A[n-1]插入到已经排序的数组A[0,...,n-2]中去。如下:
#include<stdio.h>
#include<iostream.h>
//算法导论2.3-4
//将数number插入已经排好序(非降序)的数组中
void insert(int num[],int len,int number){
int i=len-1;
while(i>=0&&num[i]>number){
num[i+1]=num[i];
i--;
}
num[i+1]=number;
}
//递归程序
void insert_into(int num[],int n){
if(n>1){
insert_into(num,n-1);
}
insert(num,n,num[n]);
}
int main(){
int num1[8]={4,2,5,1,8,10,3,7};
int len=8;
insert_into(num1,len-1);
for(int k=0;k<len;k++){
cout<<num1[k]<<endl;
}
return 0;
}
2、选择排序,时间复杂度O(n^2)
首先找出A中最小的元素,并将其与A[0]互换,接着找A中次小元素,与A[1]互换,以此类推。并且仅需要在n-1个元素上运行。伪代码如下:
for i<-1 to n-1 do//伪代码中下标从1开始
min<-Find_min(A[i,...,n])//此函数找出A中从下标i到n的最小元素,时间复杂度O(n)
min<->A[i]
endfor
根据以上伪代码可知,选择排序的时间复杂度为O(n^2)
最佳和最坏情况下的运行时间都为Θ(n²)。因为这种直接选择排序,其关键字的比较次数与各元素原来的排列顺序无关。
3、合并排序(递归法),时间复杂度O(n㏒2n)
分解:将n个元素分成各含n/2个元素的子序列
解决:用合并排序法对两个子序列进行递归排序
合并:合并两个已经排好序的子序列得到结果(merge函数)
#include<stdio.h>
#include<iostream.h>
#include <limits.h>
//算法导论p17
void merge(int num[],int p,int q,int r){//假设num[p..q],num[q+1...r]已经排好序,将其合并成一个已经排好序的数组
int n1=q-p+1;
int n2=r-q;
int *left=new int[n1+1];
int *right=new int[n2+1];
for(int i=0;i<n1;i++){
left[i]=num[p+i];
}
for(i=0;i<n2;i++){
right[i]=num[q+1+i];
}
left[n1]=INT_MAX;//哨兵牌
right[n2]=INT_MAX;
int j=0,k=0;
for(i=p;i<=r;i++){
if(left[j]>right[k]){
num[i]=right[k];
k++;
}
else{
num[i]=left[j];
j++;
}
}
for(i=p;i<=r;i++){
cout<<num[i]<<endl;
}
}
void merge_sort(int num[],int p,int r){
int q=0;
if(p<r){
q=(p+r)/2;
merge_sort(num,p,q);
merge_sort(num,q+1,r);
merge(num,p,q,r);
}
}
int main(){
int num[8]={2,4,5,7,1,2,3,6};
int p=0,r=7;
int q=(p+r)/2;
merge_sort(num,p,r);
return 0;
}
merge函数是从头开始分别比较两个子序列的数的大小,将较小的数字存入num中,相应队列的指针往后移。为了避免在每个步骤中都要检查每个序列是否为空,增加一个哨兵牌,包含一个特殊的值,来简化代码。此函数中,用正无穷来作为哨兵值,这样每当露出一张值为无穷的哨兵牌,其不可能是两张中最小的一张,除非另外一个序列也露出哨兵牌。
merge函数的时间复杂度为O(n),n=r-p+1
而合并排序,将问题分为1/2->1/4->1/8,总共分了㏒2n层,回推时每一层的代价是O(n)
4、合并排序的应用,求逆序对的个数,且算法的时间复杂度为O(n㏒2n)
逆序对:如果有i<j时,A[i]>A[j],则(i,j)叫做一个逆序对
合并排序法求逆序对的个数,当求出逆序对数目后,数组也已经排好序了,如果不想让原数组发生变化,可以把原数组拷贝到另一个数组,对新数组操作就好。整个过程一边排序一边数,递归到最深的地方如果有一个数,那么返回0,如果有两个数,看做有两个元素的子数组,两个子数组都已经排好序,并且左边只有一个元素的子数组的逆序对是0,右边只有一个元素的子数组的逆序对是0,那么两个元素的子数组的逆序对的个数就是1,若这两个数逆序,否则是0对。所以,当递归进行到最后一步,也就是这样的:a[1]到a[n/2]已经排好序,并且逆序对的个数为Inv1,a[n/2+1]到a[n]已经排好序,并且逆序对的个数为Inv2,那么整个数组的逆序对总数就是Inv1+Inv2+最后一步归并过程中的逆序对数。那么在merge这一步的逆序对个数又是怎么计算的呢?若i是左边子数组的下标(i的范围是p到q),j是右边子数组的下标(j的范围是q+1到r),假设某对(i, j),有a[i]>a[j],那么a[i]到a[q]都大于a[j],贡献的逆序对数为q-i+1。
#include<stdio.h>
#include<iostream.h>
#include <limits.h>
//算法导论p24 2-4d
int merge_num(int num[],int p,int q,int r){//假设num[p..q],num[q+1...r]已经排好序,将其合并成一个已经排好序的数组
int n1=q-p+1;
int n2=r-q;
int *left=new int[n1+1];
int *right=new int[n2+1];
int n=0;//n表示逆序对数
for(int i=0;i<n1;i++){
left[i]=num[p+i];
}
for(i=0;i<n2;i++){
right[i]=num[q+1+i];
}
left[n1]=INT_MAX;//32767
right[n2]=INT_MAX;
int j=0,k=0;
for(i=p;i<=r;i++){
if(left[j]>right[k]){
num[i]=right[k];
k++;
n+=n1-j;//注意不可是q-j+1,因为q和j指的意义不同,一个是原来数组里的下标,一个是新数组里的下标
}
else{
num[i]=left[j];
j++;
}
}
return n;
}
int merge_sort(int num[],int p,int r){
int q=0;
int total=0;
if(p<r){
q=(p+r)/2;
total+=merge_sort(num,p,q);//之前的左半部
total+=merge_sort(num,q+1,r);//之前的右半部分
total+=merge_num(num,p,q,r);//这次合并时加上total
}
return total;
}
int main(){
int num[8]={8,7,6,5,4,3,2,1};
int p=0,r=7;
int q=(p+r)/2;
//cout<<merge_num(num,p,q,r)<<endl;
cout<<merge_sort(num,p,r)<<endl;
for(int i=p;i<=r;i++){
cout<<num[i]<<endl;
}
return 0;
}
此时,因为要记录每次递归的逆序对数,因此,递归函数merge_sort中total的写法需要注意。
5、折半查找(递归程序),时间复杂度O(㏒2n)
int binary_search(int num,int num_arr[],int start,int end){
int mid=0;
if(start>=end&&num_arr[start]!=num){//注意递归终止条件
return 0;
}
mid=(start+end)/2;
if(num==num_arr[mid]){
return mid;
}
else if(num<num_arr[mid]){
binary_search(num,num_arr,start,mid);
}
else{
binary_search(num,num_arr,mid,end);
}
}
分享到:
相关推荐
实现了选择排序(SeletionSort),插入排序(InsertionSort),自底向上排序(BottomupSort),合并排序(MergeSort)和快速排序(QuickSort)
以下是对标题和描述中提到的几种排序算法的详细解析: 1. **插入排序**(Insertion Sort): - 插入排序是一种简单直观的排序算法,它的工作原理类似于打扑克牌。将数组分为已排序区和未排序区,每次从未排序区...
本资源包含了几种常见的排序算法,包括堆排序、选择排序、冒泡排序、归并排序和插入排序。这些排序算法各有特点,适用于不同的场景,并且在理解它们的工作原理后,能够帮助初学者更好地掌握编程基础。 1. **堆排序*...
本篇文章将详细讨论几种常见的排序算法:选择排序、冒泡排序、插入排序、合并排序以及快速排序,分析它们的算法原理、时间效率,并通过经验分析验证理论分析的准确性。 **1. 选择排序(Selection Sort)** 选择排序...
本篇将详细阐述标题和描述中提到的几种线性表排序算法,包括插入排序、希尔排序、冒泡排序、快速排序、堆排序以及归并排序。 1. **插入排序**: 插入排序是一种简单直观的排序算法,它的工作原理是通过构造一个...
直接插入排序(Straight Insertion Sort)是一种简单且古老的排序算法,其基本思想是将一个记录插入到已经排好序的有序表中,从而得到一个新的、记录数增1的有序表。12 直接插入排序的算法过程如下: 假设待排序...
根据给定的文件信息,我们将深入探讨几种经典的排序算法,包括选择排序、冒泡排序、交换排序、希尔排序、插入排序以及基数排序。这些算法在计算机科学领域内有着广泛的应用,各自具有独特的特点和适用场景。 ### 1....
2. **插入排序**:插入排序的工作原理是将数组分为已排序和未排序两部分,然后逐个将未排序的元素插入到已排序部分的正确位置。在Java中,可以使用一个外层循环来遍历未排序部分,再用一个内层循环找到插入位置,并...
在这个数据结构报告中,我们将深入探讨七种不同的内排序算法:简单选择排序、冒泡排序、插入排序、快速排序、两路合并排序以及堆排序。这些排序算法在C++语言环境下进行了实现,并且包含了详细的源代码和实验报告,...
根据给定的信息,本文将详细解释C#中的几种基本排序算法:选择排序、冒泡排序、快速排序、插入排序、希尔排序以及归并排序的基本原理和实现方式。 ### 一、选择排序(Selection Sort) #### 算法原理 选择排序是一...
4. **希尔排序**:希尔排序是插入排序的一种优化版本,通过比较相距一定间隔的元素,将元素逐步拉近到有序状态,最后再进行一次简单的插入排序,大大提高了效率。 5. **直接插入排序**:直接插入排序是将未排序的...
本文将深入探讨标题中提到的几种基于比较的排序算法:选择排序、插入排序、归并排序、快速排序、堆排序、冒泡排序以及希尔排序。 1. **选择排序(Selection Sort)**: - 基本思想:在未排序的序列中找到最小(或...
本项目旨在实现并比较六种经典的排序算法——直接插入排序、折半插入排序、起泡排序、简单选择排序、堆排序以及2-路归并排序,使用C语言编程。为了全面评估这些算法,我们将在一组随机生成的30000个整数上运行它们,...
每种排序算法都有其适用场景,例如,快速排序和归并排序在大数据量时表现出色,而冒泡排序和插入排序则适合小规模数据。理解并熟练掌握这些排序算法的C语言实现,对于提升编程技能和解决实际问题具有重要意义。在...
在本压缩包中,我们主要关注的是几种简单的算法,它们都是用C语言编写的,并且配合有HTML动画演示,帮助理解和学习。以下是这些算法的详细解释: 1. 插入排序(Insertion Sort) 插入排序是一种基础且直观的排序...
这里我们将深入探讨几种常见的排序算法,并在VS2013环境下进行实现和比较。 1. 冒泡排序(Bubble Sort) 冒泡排序是一种简单的交换排序,它通过重复遍历待排序的数列,依次比较相邻元素并根据需要交换位置,直到...
本篇文章将深入探讨几种常见的排序算法的C++实现,包括冒泡排序、选择排序、插入排序、快速排序、归并排序以及堆排序。 1. **冒泡排序**: 冒泡排序是最基础的排序算法之一,它通过重复遍历待排序的数列,依次比较...
在本文中,我们将探讨几种在C++中最常见的排序算法。这五种算法分别是桶排序、快速排序、归并排序、插入排序和qsort函数。每种算法都有其适用场景、优缺点、时间复杂度和空间复杂度。理解这些算法对于提升编程技能和...
1、常见排序算法实现(1-6选择几个算法练习) 1)问题描述:输入一组关键字序列分别实现下列排序。 (1)实现简单选择排序、直接插入...2、在上题的基础上增加功能(程序改名另存):增加变量统计每一种排序的比较次数.
本文将深入探讨几种常见的数组排序算法,包括插入排序、交换排序、选择排序和归并排序,以及基数排序。这些算法在不同的场景下有不同的效率表现,选择合适的排序方法对程序性能有着显著影响。 1. 插入排序: 插入...