排序算法总结(内排序)

        排序算法作为算法和数据结构的重要部分,系统地学习一下是很有必要的。排序是计算机内经常进行的一种操作,其目的是将一组“无序”的记录序列调整为“有序”的记录序列。

        排序分为内部排序和外部排序:

  • 若整个排序过程不需要访问外存便能完成,则称此类排序问题为内部排序;
  • 反之,若参加排序的记录数量很大,整个序列的排序过程不可能在内存中完成,则称此类排序问题为外部排序。

        常说的的八大排序算法均属于内部排序。如果按照策略来分类,大致可分为:交换排序、插入排序、选择排序、归并排序和基数排序。如下图所示:

        下表给出各种排序的基本性能,本篇文章不做详细介绍,具体分析请参看其他博客的详解:

1. 交换排序

1.1 冒泡排序

        基本思想:比较相邻元素,每比较一轮,将最大的元素置于比较区间内的最后一个元素。优化点:如果哪一轮比较发现没有需要交换的元素,排序即结束。时间复杂度:平均和最坏情况--O(n的平方),最好情况:O(n),此时给定序列已经排好序。

void bubbleSort(vector<int>& nums) {
	int n = nums.size();
	bool needSwap = true;

	for(int i = 1; i < n && needSwap; i++) {
		bool needSwap = false;
		for(int j = 0; j < n - i; j++) {
			if(nums[j] > nums[j+1]) {
				swap(nums[j], nums[j+1]);
				needSwap = true;
			}
		}
	}
}

1.2 快速排序

        基本思想:保证区间内每个元素的所有左边元素都小于该元素,所有右边元素都大于该元素。时间复杂度平均情况为:O(nlogn),最坏情况:O(n的平方)(每次选的基准点的元素为最大元素或者最小元素),最好情况: O(nlogn)。

int partition(vector<int>& nums, int left, int right) {
	int pivotValue = nums[left];

	while(left < right) {
		while(left < right && nums[right] >= pivotValue)
			right--;
		nums[left] = nums[right];

		while(left < right && nums[left] <= pivotValue)
			left++;
		nums[right] = nums[left];
	}
	nums[left] = pivotValue;
	return left;
}

void quickSort(vector<int>& nums, int left, int right) {
	if(left < right) {
		int pivotInd = partition(nums, left, right);
		quickSort(nums, left, pivotInd - 1);
		quickSort(nums, pivotInd + 1, right);
	}
}

2. 插入排序

2.1 直接插入排序

        基本思想:处理下标为i的元素时,0~i-1的元素已经排好序,需要找到下标为i元素的插入位置。时间复杂度平均情况为:O(n的平方),最坏情况:O(n的平方),最好情况: O(n),此时给定序列已经排好序。

void insertSort(vector<int>& nums) {
	int n = nums.size();

	int i = 0, j = 0;
	for(i = 1; i < n; i++) {
		int temp = nums[i];
		for(j = i - 1; j >= 0; j--) {
			if(nums[j] > temp) {
				nums[j + 1] = nums[j];
			} else {
				break;
			}
		}
		nums[j+1] = temp;
	}
}

2.2 希尔排序

        基本思想:不断缩小插入排序的步长,直到为0时退出,排序结束。时间复杂度平均情况为:O(nlogn),最坏情况:O(n的平方),最好情况: 未知,大概n的1.3次方左右。

void shellShort(vector<int>& nums) {
	int n = nums.size();
	int gap = n / 2;
	int i = 0, j = 0;

	while(gap > 0) {
		for(i = gap; i < n; i++) {
			int temp = nums[i];
			for(j = i - gap; j >= 0; j = j - gap) {
				if(nums[j] > temp) {
					nums[j + gap]  = nums[j];
				} else {
					break;
				}
			}
			nums[j + gap] = temp;
		}
		gap = gap / 2;
	}
}

3. 选择排序

3.1 简单选择排序

        基本思想:每次选中排序区间内的最小元素(记录其下标),置于排序区间的第一个位置。其平均、最好,最坏时间复杂度均为O(n的平方)。

void simpleSelectSort(vector<int>& nums) {
	int n = nums.size();
	for(int i = 0; i < n; i++) {
		int minInd = i;
		for(int j = i + 1; j < n; j++) {
			if(nums[j] < nums[i])
				minInd = j;
		}

		if(minInd != i) {
			swap(nums[i], nums[minInd]);
		}
	}
}

3.2 堆排序

        基本思想:将数组视为一颗二叉树,下标为i的元素,其左右树的下标分别为2*i+1, 2*i+2(如果存在的话)。每次取出堆顶元素,进行堆的调整,如果往复,直到堆的大小为1,直接取出堆顶元素即可。其平均、最好,最坏时间复杂度均为O(nlogn)。

  • 如果是升序排序,则采用大顶堆,每次取出当前待排元素中的最大值,将其与最后一个位置的元素进行交换。
  • 如果是降序排序,则采用小顶堆,每次取出当前待排元素中的最小值,将其与最后一个位置的元素进行交换。
void headAjust(vector<int>& nums, int parent, int heapSize) {
	int left = 2 * parent + 1;
	int right = 2 * parent + 2;
	int largestInd = parent;

	if(left < heapSize && nums[left] > nums[largestInd])
		largestInd = left;

	if(right < heapSize && nums[right] > nums[largestInd])
		largestInd = right;

	if(largestInd != parent) {
		swap(nums[parent], nums[largestInd]);
		headAjust(nums, largestInd, heapSize);
	}
}

void heapSort(vector<int>& nums) {
	int n = nums.size();
	for(int i = n / 2 - 1; i >= 0; i--) {
		headAjust(nums, i, n);
	}

	for(int i = n; i > 0; i--) {
		headAjust(nums, 0, i);
		swap(nums[0], nums[i - 1]);
	}
}

4. 归并排序

        基本思想:采用分治的思想,先分成很小的子序列,然后依次合并(两路),最后得到排序数组。其平均、最好,最坏时间复杂度均为O(nlogn)。

void merge(vector<int>& nums, int left, int right) {
	int mid = (left + right) / 2;
	int *temp = new int[right - left + 1];

	int i = left, j = mid + 1, k = 0;
	while(i <= mid && j <= right) {
		if(nums[i] < nums[j]) {
			temp[k++] = nums[i];
			i++;
		} else {
			temp[k++] = nums[j];
			j++;
		}
	}

	while(i <= mid)
		temp[k++] = nums[i++];

	while(j <= right)
		temp[k++] = nums[j++];

	for(int i = left; i <= right; i++) {
		nums[i] = temp[i - left];
	}

	delete []temp;
}

void mergeSort(vector<int>& nums, int left, int right) {
	if(left < right) {
		int mid = (left + right) / 2;
		mergeSort(nums, left, mid);
		mergeSort(nums, mid+1, right);
		merge(nums, left, right);
	}
}

5. 基数排序

        基本思想:

(1)找出待排序的数组中最大和最小的元素

(2)统计数组中每个值为i的元素出现的次数,存入数组的第i项

(3)对所有的计数累加(从数组中的第一个元素开始,每一项和前一项相加)

(4)反向填充目标数组:将每个元素i放在新数组的第(i)项,每放一个元素就将(i)减去1

void countSort(vector<int>& nums)
{
	int n = nums.size();
	if(n <= 1) return;

        //确定数组最大值和最小值
        int max = nums[0], min = nums[0];
        for (int i = 1; i < n; i++)
        { if (nums[i] > max)
                max = nums[i];
            if (nums[i] < min)
                min = nums[i];
        }

        int len = max - min + 1;
        // 确定统计数组长度并进行初始化
        int* count = new int[len];
        for (int i = 0; i <= len; ++i)
            count[i] = 0;

        // 遍历数组,统计每个数出现的次数
        for (int i = 0; i < n; ++i)
                ++count[nums[i] - min];

        // 统计数组做变形,后面的元素个数等于前面的元素个数之和, 再加上自身元素的个数
        for (int i = 1; i <= len; ++i)
            count[i] += count[i - 1];

    	// 倒序遍历原始数列,从统计数组找到正确的位置,输出到结果数组
        int* sortedArray = new int[n];
        for (int i = n - 1; i >= 0; i--)
        {
            sortedArray[count[nums[i] - min] - 1] = nums[i];        // 找到nums[i]对应的count的值,值为多少,表示原来排序多少,(因为从1开始,所以再减1)
            count[nums[i] - min]--;        // 然后将相应的count的值减1,表示下次再遇到此值时,原来的排序是多少。
        }

	for(int i = 0; i < n; i++) {
		nums[i] = sortedArray[i];
	}
}

6. 参考资料

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值