C++ 算法 排序 分析


所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。排序算法,就是如何使得记录按照要求排列的方法。排序算法在很多领域得到相当地重视,尤其是在大量数据的处理方面。一个优秀的算法可以节省大量的资源。在各个领域中考虑到数据的各种限制和规范,要得到一个符合实际的优秀算法,得经过大量的推理和分析。(注:百度百科)

注:

  1. 排序算法:可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。
  2. 算法稳定性.:在待排序的数据中,存在多个相同的数据,经过排序之后,他们的对相对顺序依旧保持不变,实际上就是说array[i]=array[j],i<j.就是array[i]在array[j]之前,那么经过排序之后array[i]依旧在array[j]之前,那么这个排序算法稳定,否则,这个排序算法不稳定

1. 冒泡排序

a. 原理

重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成。因为越小的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。(注:百度百科)

b.代码

//elemType 为数组元素类型,常见有 int,long,char,short ,double,float等;也可以是自定义类型,如不改变以下格式,可以尝试重载 “<” 符号;
void bubbleSort (elemType arr[], int len) {
	elemType changeTemp;
	for (int i = 0; i < len-1; i++) {
		for (int j = 1;j < len -i; j++) {
			if (arr[j] < arr[j-1]) {
				changeTemp = arr[j];
				arr[i] = arr[j - 1];
				arr[j - 1]  = changeTemp;			
			}
		}
	}
}

c.解释

冒泡排序 (稳定排序算法)

  1. 从当前元素起,向后依次比较每一对相邻元素,若逆序则交换
  2. 对所有元素均重复以上步骤,直至最后一个元素有序;elemType arr[]: 排序目标数组; int len: 元素个数
  3. 时间复杂度:O(n²),原因是:使用了双层for循环;

2. 选择排序

a. 原理

第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。

b.代码

void selectSort(elemType arr[],int lenght) {
	int minIndex  = -1;
	for (int i = 0; i < lenght-1; i++) {
		minIndex = i;
		for (int j = i+1;j < length; j++) {
			if ( arr[j] < arr[minIndex]) {
				minIndex = j;
			}
		}
		if (minIndex != i ) {
				exchange arr[i] and arr[minIndex];
		}	
	}
}

c.解释

  1. 对于length 长度的数组需要执行 length -1 次迭代,每次迭代的过程如下:
    1)第一次:未排序序列中找到最小(大)元素,存放到排序序列的起始位置
    2)非第一次:剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末 尾。
  2. 选择排序:不稳定排序(具体原因见上述算法稳定性)
  3. 时间复杂度:O(n²),原因是:使用了双层for循环;

3. 插入排序

a. 原理

插入排序是指在待排序的元素中,假设前面n-1(其中n>=2)个数已经是排好顺序的,现将第n个数插到前面已经排好的序列中,然后找到合适自己的位置,使得插入第n个数的这个序列也是排好顺序的。按照此法对所有元素进行插入,直到整个序列排为有序的过程,称为插入排序 。

b.代码

void insertSort(elemType arr[], int length) {
	elemType changeTemp;
	int  j = 0;
	for (int i = 1; i < length ; i++ ) {
		changeTemp = arr[i];
		for (j = i-1; j >= 0; j--) {
		   if (arr[j] < changeTemp) {
				 break;
		   }
		  arr[j+1] = arr[j];
		}
		arr[j+1] = changeTemp
	}
}

c.解释

  1. 同上,对应长度为length的数组只需要执行length-1次迭代,将length-1 个元素排好序即可。从第二个元素(下标为1)开始迭代,具体过程如下:
    将待插入元素(下标为i)与前面排序序列从右向左依次对比,若待插入元素较小,则将排序序列中被比较的元素(下标为j)右移一位,否则,将待插入元素放入(j+1)的位置;
  2. 插入排序:稳定排序;
    时间复杂度:O(n²)

4. 快速排序

a. 原理

快速排序: 简单来说可以概括为东拆西补 或 西拆东补,一边拆一边补,每次递归实现了将数组的小于标准元素(可以指定数组中任意一个,一边取第一个元素)的元素放在左侧,大于标准元素的放在右侧;对左右两侧的元素执行相同的操作(一般用递归实现)

b.代码

int getSortIndex(elemType arr[],int low,int high) {
    elemType standard = arr[low];			//取第一个元素作为标准元素,造成一个空位;
    while(low < high) {
    	//从右侧寻找 比standard 小的元素;补左侧空位;
        while(low < high && arr[high] >= standard) {    
            high--;
        }
        arr[low] = arr[high];
        //从左侧寻找 比standard 大的元素;补右侧空位;
        while(low < high && arr[low] <= standard) {		
            low++;
        }
        arr[high] = arr[low];
    }
    //最后将标准元素填充到最后的空位上
    arr[low] = standard;							
    return low;		//返回不需要再变动元素的位置
}

void quickSort(elemType arr[], int low, int high) {
    if (low < high) {
        int index = getSortIndex(arr,low,high);
        quickSort(arr,low,index-1);
        quickSort(arr,index+1,high);
    }
}

c.解释

  1. 代码采用递归的实现,每次递归(满足low < high)执行三个动作:
    (1). 对数组进行东拆西补,西拆东补,直至将大于标准元素和小于标准元素分开,并返回不需要再变动元素的位置;
    (2). 对小于标准元素的数组进行递归;
    (3). 对大于标准元素的数组进行递归;

  2. 快速排序:不稳定排序

  3. 时间复杂度:O(nlog₂n)

5. 堆排序

a. 原理

堆排序(英语:Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

b.代码

void maxHeapify(elemType arr[],int start, int end) {
	int parent = start;
	int child = 2 * parent + 1;  //left child
	while(child <= end) {
		if (child + 1 <= end && arr[child] < arr[child + 1] ) {
			child++;   //right child
		}
		if (arr[parent] >arr[child]) {
			return;   //不需要交换
		} else {
			exchange arr[parent] and arr[right];
			parent = child;
		 	child = 2 * parent + 1;
		}
	}
}

void heapsort(elemType arr[],int length) {
	int  i ;
	//初始化,i 从最后一个父节点开始调整
	for (i = length / 2 -1; i >= 0; i--) {
		maxHeapify(arr,i,length-1);
	}
	for (i = length -1; i > 0 ;i--) {
		exchange arr[0] and arr[i];
		maxHeapify(arr,0, i-1);
	}
}

c.解释

  1. 在堆的数据结构中,堆中的最大值总是位于根节点(在优先队列中使用堆的话堆中的最小值位于根节点)。堆中定义以下几种操作:
    (1). 最大堆调整(Max Heapify):将堆的末端子节点作调整,使得子节点永远小于父节点;
    (2). 创建最大堆(Build Max Heap):将堆中的所有数据重新排序
    (3). 堆排序(HeapSort):移除位在第一个数据的根节点,并做最大堆调整的递归运算

  2. 最大堆排序:不稳定排序

  3. 时间复杂度:O(nlog₂n)

6.计数排序

a. 原理

计数排序对输入的数据有附加的限制条件:
1、输入的线性表的元素属于有限偏序集S;
2、设输入的线性表的长度为n,|S|=k(表示集合S中元素的总数目为k),则k=O(n)。

在这两个条件下,计数排序的复杂性为O(n)。
计数排序的基本思想是对于给定的输入序列中的每一个元素x,确定该序列中值小于x的元素的个数(此处并非比较各元素的大小,而是通过对元素值的计数和计数值的累加来确定)。一旦有了这个信息,就可以将x直接存放到最终的输出序列的正确位置上。例如,如果输入序列中只有17个元素的值小于x的值,则x可以直接存放在输出序列的第18个位置上。当然,如果有多个元素具有相同的值时,我们不能将这些元素放在输出序列的同一个位置上,因此,上述方案还要作适当的修改。

b.代码

void CountSort(int data[],int n) {
    int i,j,count,*data_p,temp;
    data_p = new int[n];
    //初始化data_p
    for(i = 0; i < n; i++) {
        data_p[i] = 0;
    }
    for(i = 0; i < n; i++) {
        count = 0;
        //扫描待排序数组
        for(j = 0;j < n; j++) {
           //统计比data[i]值小的值的个数
           if(data[j] < data[i]) {
                count++;
           }
        }
        if(data[j] < data[i]) {
            count++;
        }//对于相等非0的数据,应向后措一位。数据为0时,因数组data_p被初始化为0,故不受影响。
        while (data_p[count] != 0) {
        /* 注意此处应使用while循环进行判断,若用if条件则超过三个重复值后有0出现 */
                count++;
        }
        data_p[count] = data[i];//存放到data_p中的对应位置
    }
        //用于检查当有多个数相同时的情况
    i = 0,j = n;
    while(i < j) {
        if(data_p[i] == 0) {
            temp = i-1;
            data_p[i] = data_p[temp];
        }//of if
        i++;
    }//of  while
    for(i = 0;i < n; i++)//把排序完的数据复制到data中
        data[i] = data_p[i];
    delete []data_p;//释放data_p
}

c.解释

假设输入的线性表L的长度为n,L=L1,L2,…,Ln;线性表的元素属于有限偏序集S,|S|=k且k=O(n),S={S1,S2,…Sk};则计数排序可以描述如下:
1、扫描整个集合S,对每一个Si∈S,找到在线性表L中小于等于Si的元素的个数T(Si);
2、扫描整个线性表L,对L中的每一个元素Li,将Li放在输出线性表的第T(Li)个位置上,并将T(Li)减1。

注:在 C++ 中,我们也许经常使用需要建立数组和释放内存的问题,在动态分配内存的时候一般有两种方式,一个是malloc和new两种形式。
比如使用malloc的时候,一般形式如下:
har *Ptr = NULL;
Ptr = (char *)malloc(100 * sizeof(char));
if (NULL == Ptr) {
exit (1);
}
gets(Ptr);
free(Ptr);
在使用new的时候,一般的形式如下:

  1. 删除单变量地址空间
    int *a = new int;
    delete a; //释放单个int的空间
  2. 删除数组空间
    int *a = new int[5];
    delete []a; //释放int数组空间

7.基数排序

a. 原理

基数排序的发明可以追溯到1887年赫尔曼·何乐礼在打孔卡片制表机(Tabulation Machine)上的贡献。它是这样实现的:将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
基数排序的方式可以采用LSD(Least significant digital)或MSD(Most significant digital),LSD的排序方式由键值的最右边开始,而MSD则相反,由键值的最左边开始。

b.代码

/**
* @brief      make the all array be sorted by the radix way
* @param  arr: the address of the array
* @param  len: the length of the array
* @param  max: The number of digits of the largest element in the array
*/
void radixSort(int arr[],int len ,int maxDigit) {

	int k = 0;
	int n = 1;
	int m = 1; //控制键值排序为哪一位
	int temp[10][len] = {0}; //数组的第一维表示可能的余数0-9
	int order[10] = {0};
	while (m <= maxDigit)  {
		for (int i = 0; i < len; i++) {
			int lsd = ((arr[i] / n) % 10);
			temp[lsd][order[lsd]] = arr[i];
			order[lsd]++;
		}
		for (int i = 0; i < 10; i++){
			if (order[i] != 0) {
				for (int j = 0; j <order[i]; j++) {
					arr[k] = temp[i][j];
					k++;
				}
				order[i] = 0;
			}
		}
		n *= 10;
		k = 0;
		m++;
	}
}

c.解释

假设数组元素的最高位数N,那么需要迭代N次,直至数组在(个位,十位,百位,千位…)的所有基数上都有序,那么可以认为整个数组是有序的

8.希尔排序

a. 原理

b.代码

c.解释

9.归并排序

a. 原理

第一步:申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
第二步:设定两个下标,最初位置分别为两个已经排序序列的起始位置
第三步:比较两个下标所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
重复步骤3直到某一下标超出序列尾
将另一序列剩下的所有元素直接复制到合并序列尾

b.代码

void mergeArr(elemType arr[],int low,int mid,int high) {
	int *buf = new elemType[high - low + 1];
	int i = low;
	int j = mid + 1;
	int bufIndex = 0;
	while ( i <= mid  &&  i <= high) {
		if ( nums[i] < nums[j]) {
			buf[bufIndex++] = arr[i++];
		} else {
			buf[bufIndex++] = arr[j++];
		}
	}
	while(i <= mid){
		buf[bufIndex++] = arr[i++];
	} 
	while(j <= mid) {
		buf[bufIndex++] = arr[j++];
	}
	for(int k = 0; k < (right - left + 1); k++) {
		arr[left+k] = buf[k];
	} 
	delete[] buf;
}


void mergeSort(elemType arr[], int low, int high) {
	if (low< high) {
		int mid = low+(high- low)/ 2;
		mergeSort(arr,low,mid);
	    mergeSort(arr,mid+1,high);
	    mergeArr(arr,low,mid,high);
	}
}

c.解释

  1. 合并的两个数组必须有序;
  2. 递归实现数组有序;
  3. 思想:假设每次递归实现了数组有序递归内部实现:
    a. 递归实现左边数组有序;
    b. 递归实现右边数组有序;
    c. 合并有序的左边数组和右边数组,达到整个数组有序;

猜你喜欢

转载自blog.csdn.net/qq_34262886/article/details/107787885
今日推荐