CSDN的名言——编程之久,除算法和数据结构,啥都不属于我们。做完一些项目发现,编程最重要的还是算法还有数据结构,无关语言、框架。所以有感需要重拾一下一些算法。作为入口,希望排序带我不断地探索出去。
——————————————以下实现均实现为升序排序————————————————
算法思想很简单,通过两两比较,不断把较大的那个挤向边沿,形成有序序列。
是稳定的排序,即排序后相同数值的元素间的序列不变。
实现简单,所需空间小,时间复杂度O(N^2),但交换频繁。
// 冒泡 升序
void bubbleSort(int* array, int len){
for(int i = 0 ; i < len ; i++){
for(int j = 1; j < len-i ; j++){
if(array[j-1] > array[j]){
int temp = array[j-1]; // 替换
array[j-1] = array[j];
array[j] = temp;
}
}
}
}
通过遍历未排序的序列,找出最大的元素的索引,将其与边沿的元素交换。
因为存在边沿元素被交换至前边的可能,所以该算法不是稳定的。
思想很简单,最符合人的思维模式。所需空间小,时间复杂度O(N^2),交换次数小。
// 选择 升序
void selectionSort(int* array, int len){
for(int i = 0 ; i < len ; i++){
int maxIndex = 0 ; // 索引
for(int j = 1; j < len - i ; j++){
if(array[j] > array[maxIndex]){
maxIndex = j;
}
}
if(maxIndex != len - i - 1){
int temp = array[len-i-1]; // 替换
array[len-i-1] = array[maxIndex];
array[maxIndex] = temp;
}
}
}
通过将待排序的序列一个个地插入到已排序序列合适的位置实现的算法。
按照顺序插入,能够保证相同数值元素间的序列不变,是稳定的。
所需空间小,时间复杂度O(N^2),但交换次数多,需频繁挪动已排序的序列。
// 插入 升序
void insertionSort(int* array, int len){
for(int i = 1 ; i < len ; i++){
for(int j = i ; j > 0 ; j--){
if(array[j] >= array[j-1]){
break;
}
int temp; // 向前 插入
temp = array[j];
array[j] = array[j-1];
array[j-1] = temp;
}
}
}
顾名思义,该算法是基于计数而非比较的。通过将具有相同数值的元素个数计算起来,再从头重构起整一个已排序序列。
局限性大,只能排列整数序列,需要明确知道序列的范围大小,且范围不能太大。
但在范围较小的整数序列中,优势很明显,就是快。空间复杂度O(M)(M为范围大小),时间复杂度O(N+M)。
// 计数 升序
// 排序 数值范围[0, 99]
int countingArray[100];
void countingSort(int* array, int len){
// 每次调用需保证countArray 数组元素均为0
for(int i = 0 ; i < len ; i++){
countingArray[array[i]]++; // 计数
}
int index = 0 ;
for(int i = 0 ; i < 100; ){
if(countingArray[i]){ // 重构
array[index++] = i ;
countingArray[i]--;
if(index == len)
break;
}else{
i++;
}
}
}
非基于比较,通过元素的每一位基数不断调整序列的算法。
基本思想为:对于每一位基数进行排序,前一位基数较大的将在以后的位数排序都处在较后的位置,直到最后一位排列完,序列将呈升序序列。
优势是快,但实现较为复杂,空间开销大,每一个基数桶子需保证能够装下整一个序列。
以下实现不具有通用性,不能随意切换radix,仅考虑使用位运算而采用二进制。
// 基数 升序
// 二进制 即只有 0 1 两个桶
int radixs[2][100];
int lens[2];
void radixSort(int* array, int len){
bool stop = false;
int radix = 1;
int bits = 0 ;
while(!stop){
stop = true;
for(int i = 0 ; i < len ; i++){ // 分类
int itsRadix = (array[i] & radix) >> bits;
radixs[itsRadix][lens[itsRadix]++] = array[i];
stop = array[i] > (1 << bits) ? false:stop; // 退出
}
int index = 0;
for(int i = 0; i < 2 ; i++){ // 重构
for(int j = 0 ; j < lens[i]; j++){
array[index++] = radixs[i][j];
}
lens[i] = 0;
}
radix *= 2;
bits++;
}
}
基于堆的概念:小根堆——父节点永远不比子节点大。
算法两个很重要的步骤:建堆,把新来的元素放在堆的最后,然后调整堆,不断比较与父节点的大小,如果比父节点小则交换,如此操作直到不比父节点小或已到达堆顶;出堆,把处在堆顶的元素取下,把堆最后一个元素放在堆顶,然后调整堆,与最小子节点比较,若比子节点小则交换,直到符合父节点不比子节点小或没有子节点。
由于堆中最后一个元素会频繁置换到堆顶,所以不能保证稳定性。
空间复杂度O(N),时间复杂度O(N*log(N))
// 堆
// 小根堆
int heap[100];
void heapSort(int* array, int len){
for(int i = 0; i < len ; i++){
int last = i+1;
heap[last] = array[i]; // 建堆
while(last>>1){
if(heap[last] < heap[last>>1]){
int temp = heap[last]; // 替换
heap[last] = heap[last>>1];
heap[last>>1] = temp;
}
last = last >> 1;
}
}
int heapLen = len;
for(int i = 0 ; i < len; i++){
array[i] = heap[1]; // 出堆
heap[1] = heap[heapLen--];
int last = 1;
while((last << 1) <= heapLen){
int max = last << 1;
if(max+1<=heapLen && heap[max] > heap[max+1]){
max++; // 最小子节点
}
if(heap[last] > heap[max]){
int temp = heap[last];
heap[last] = heap[max];
heap[max] = temp;
last = last << 1;
}else{
break;
}
}
}
}
如果对递归非常了解的话,该排序思想是十分简单明了的。
通过将序列分成两部分,各自分给下级完成那部分的排序工作,当两部分都完成后,再在这级将两部分合并成有序序列,交付给上级。
由于两部分都是有序序列,所以合并起来的时候能够减少很多比较,从而节省了时间,并且保证稳定性。
时间复杂度O(N*log(N))
// 归并
void mergeSort(int* array, int len){
if(len == 1){
return;
}else if(len == 2){
if(array[0] > array[1]){
int temp = array[0];
array[0] = array[1];
array[1] = temp;
}
return;
}else{
int leftLen = len / 2 ;
mergeSort(array,leftLen); // 递归 左
mergeSort(&array[leftLen],len-leftLen); // 右
//合并
int* tempArray = new int[len];
int left = 0;
int right = leftLen;
for(int i = 0 ; i < len; i++){ // 合并
if(array[left] > array[right]){
tempArray[i] = array[right++];
if(right == len){
for(int j = left ; j < leftLen; j++){
tempArray[++i] = array[j];
}
}
}else{
tempArray[i] = array[left++];
if(left == leftLen){
for(int j = right; j < len ; j++){
tempArray[++i] = array[j];
}
}
}
}
for(int i = 0 ; i < len ; i++) // copy
array[i] = tempArray[i];
delete [] tempArray;
}
}
与归并排序类似的思想,但实现却相反的算法。在序列中选定一个pivotpos索引,然后将该序列逐一与该pivotpos索引下的数值比较,比它小放置在左边,比它大的放置右边,pivotpos放置在中间(非绝对中间,比它小的元素和比它大的元素的中间),然后将左边和中间的序列交给下级,右边的交给另一个下级。
时间复杂度为O(N*log(N)),但相比归并,快排的递归放在尾部,因此对栈的压力比归并大。但是在进行序列操作的时候,快排显得更自然,而归并在合并两个序列的时候,逻辑会更复杂一些。
// 快排
void quickSort(int* array, int len){
if(len == 1 || len == 0){
return;
}else if(len == 2){
if(array[0] > array[1]){
int temp = array[0];
array[0] = array[1];
array[1] = temp;
}
return;
}else{
int pivotpos = 0;
int last = len-1;
int* tempArray = new int[len];
for(int i = 1; i < len ; i ++){
if(array[i] > array[0] ){ // 分类
tempArray[last--] = array[i];
}else{
tempArray[pivotpos++] = array[i];
}
}
tempArray[pivotpos] = array[0]; //基点
for(int i = 0 ; i < len ; i++){
array[i] = tempArray[i];
}
delete [] tempArray;
quickSort(array,pivotpos+1); // 左
quickSort(&array[pivotpos+1],len-pivotpos-1); // 右
}
}