C语言常用排序算法原理及实现

一、冒泡排序

思路:每次冒泡,从第0个元素开始,相邻两个元素之间进行比较
如果元素大,则往后冒,否则不作交换
这样,一次冒泡(即一层循环)下来之后,就可以确定一个最大值且排在最后的位置
接下来的循环就对剩余的元素重复之前的操作,相邻相比,大者冒泡,找到第二大元素
以此类推,直到最后所有元素都到了相应的位置,冒泡结束

复杂度:O(n^2)
稳定性:是稳定的

void
bubbleSort(int* array, int length) {
    
    
	for (int i = 0; i < length - 1; i++) {
    
    
		for (int j = 0; j < length - i - 1; j++) {
    
    
			if (array[j] > array[j + 1])
				swap(&array[j], &array[j + 1]);
		}
	}
}

二、选择排序

思路:每次遍历整个范围,初始范围为整个数组,找到最大的数字和最小的数字
分别把最大放在最后、最小放在最前,然后下标移动,头往后,尾往前,范围向内缩小
重复上述过程,每一次都找到该搜索范围内的最小和最大并分别放到开头和结尾位置
直到头尾相碰,范围缩小为0,则排序结束

复杂度:O(n^2)
稳定性:不稳定的

这里用的是优化版的选择排序,即每趟遍历确定剩余元素的最大值和最小值

void
selectSort(int* array, int length) {
    
    
	for (int head = 0, tail = length - 1; head < tail; head++, tail--) {
    
    
		int min = head, max = tail;
		for (int i = head; i <= tail; i++) {
    
    
			if (array[i] > array[max])max = i;
			if (array[i] < array[min])min = i;
		}

		if (max == head && min == tail) {
    
    
			swap(&array[head], &array[tail]);
		} else if (max == head && min != tail) {
    
    
			swap(&array[max], &array[tail]);
			swap(&array[min], &array[head]);
		} else if (max != head && min == tail) {
    
    
			swap(&array[min], &array[head]);
			swap(&array[max], &array[tail]);
		} else {
    
    
			swap(&array[max], &array[tail]);
			swap(&array[min], &array[head]);
		}
	}
}

三、插入排序

思路:假定数组的前面部分是已经排序好的(最开始只排了一个),
然后从下标为“1”的元素开始进行一次for循环
循环中,在把当前下标的值和前面已经排序好的数字进行比较,
如果比前面的小,就和前面交换位置,直到不再比前面小,这样就实现了数字的插入
即,每次外层循环实现的都是一次“一个数字往一个有序数组中做插入”

复杂度:O(n^2)
稳定性:稳定的

void
insertSort(int* array, int length) {
    
    
	for (int i = 1; i < length; i++) {
    
    
		for (int j = i; j > 0; j--) {
    
    
			if (array[j] > array[j - 1]) {
    
    
				break;
			} else {
    
    
				swap(&array[j], &array[j - 1]);
			}
		}
	}
}

四、希尔排序

思路:按照一个步长h对数组进行分组,比如
分组之后,对于每组元素分别进行插入排序,
完成之后,再对步长进行减半处理,更新步长
然后同样的,按照步长进行分组,然后分别插入排序
直到最后步长为0,不再进行操作,则完成排序

复杂度:O(n^(3/2))
稳定性:不稳定的

void
shellSort(int* array, int length) {
    
    
	int h = 1;
	for (h = 1; h < length / 2; h = h * 2 + 1);

	while (h > 0) {
    
    
		for (int i = h; i < length; i++) {
    
    	//从所有组别中的第一个组别的第二个元素开始
			for (int j = i; j >= h; j -= h) {
    
    	//从当前组的第二个元素开始,进行插入排序
				if (array[j] > array[j - h]) {
    
    
					break;
				} else {
    
    
					swap(&array[j], &array[j - h]);
				}
			}
		} h /= 2;	//更新缩短步长
	}
}

五、归并排序

思路:把数组尽可能的等分成两份——根据数组的首元素下标和尾元素下标,可以得到中间值下标
然后将得到的两个数组再次递归调用本身,去进行进一步的划分
直到数组不可再分了——递归终止的条件——则返回
对划分好的数组,进行排序和归并,由小及大
最开始,递归的最深层,对两个数组进行排序和归并只是对两个元素进行简单比较并排序,然后归并
这样,由小及大,每一次得到的要处理的两个数组都是各自有序的
那么,要对这两个各自有序的数组进行归并,首先需要一个新的空间——这里我们是malloc一个辅助数组
然后利用三个“指针”分别指向左数组的头、右数组的头、辅助数组的头,然后开始比较左指针和右指针指向的值
然后再慢慢归并,最终就可以积小成大了

复杂度:O(nlogn)
稳定性:稳定的

static void
sortandMerge(int* array, int* assist, int head, int mid, int tail) {
    
    
	int loc = head, left = head, right = mid + 1;
	//利用三个指针对两个有序数组进行排序
	while (left <= mid && right <= tail) {
    
    	//其中一个数组遍历完则跳出循环
		if (array[left] < array[right]) {
    
    
			assist[loc++] = array[left++];
		} else {
    
    
			assist[loc++] = array[right++];
		}
	}

	//遍历可能剩下的数组中的元素
	while (left <= mid) {
    
    
		assist[loc++] = array[left++];
	}

	//遍历可能剩下的数组中的元素
	while (right <= tail) {
    
    
		assist[loc++] = array[right++];
	}

	//将归并好的数组从辅助数组copy到原数组中去
	for (int i = head; i <= tail; i++) {
    
    
		array[i] = assist[i];
	}
}


static void
separate(int* array, int* assist, int head, int tail) {
    
    
	//判断输入合法性	//由于下面mid由head加值得到,然后mid成为新的tail递归传入
	if (head >= tail)return;	//递归的终止条件	

	//计算中值,之后可将原数组分为head~mid,mid+1 ~ tail这两组
	int mid = head + (tail - head) / 2;

	//分别对两组元素进行分组,其实质是递归调用本身
	separate(array, assist, head, mid);
	separate(array, assist, mid + 1, tail);

	//将排序好的两组元素进行排序和归并
	sortandMerge(array, assist, head, mid, tail);
}


void
mergeSort(int* array, int length) {
    
    
	//构造一个辅助数组assist:
	int* assist = (int*)malloc(sizeof(int) * length);
	//head和tail变量分别记录当前数组的头和尾
	int head = 0, tail = length - 1;
	//调用separate来实现数组的排序
	separate(array, assist, head, tail);
	//释放辅助数组
	free(assist);
}

六、快速排序

思路:每次以数组的第一个元素为标准,将数组分为两等分,
左边的数比标准小,右边的数比标准大
然后得到的两个数组再分别递归进行分组操作
分组的过程实质上是“排序+求标准位置”的过程,
每次分组过后得到下一次分组的分界线,同时两组元素实现了某种程度上的有序
——即左边的比标准小,右边的比标准大

复杂度:O(nlogn)
稳定性:不稳定

快排我写得有点烂…

static int 
filterGetmid(int* array, int head, int tail) {
    
    
	int standard = head++, end = tail;
	
	while (head<tail) {
    
    
		while (tail-head>1&&array[head]<=array[standard]) head++;
		while (tail-head>1&&array[tail]>=array[standard]) tail--;

		if (1 == tail - head) {
    
    
			if (array[head] > array[tail]) {
    
    
				swap(&array[head], &array[tail]);
			}

			if (array[head] < array[standard] && array[tail] < array[standard]) {
    
    
				head++;
			} else if (array[head] >= array[standard] && array[tail] > array[standard]) {
    
    
				tail = --head;
			} else {
    
    
				break;
			}
		}

		if (array[head] > array[standard] && array[tail] < array[standard]) {
    
    
			swap(&array[head], &array[tail]);
		}
	} //while
	
	if (array[head] < array[standard]) {
    
    
		swap(&array[head], &array[standard]);
	}
	
	return head;
}

static void
separate(int* array, int head, int tail) {
    
    
	if (head >= tail)return;

	int mid = filterGetmid(array, head, tail);

	separate(array, head, mid-1);
	separate(array, mid+1, tail);
}


void 
quickSort(int* array, int length) {
    
    
	int head = 0, tail = length - 1;
	separate(array, head, tail);
}

猜你喜欢

转载自blog.csdn.net/weixin_44692935/article/details/108681471