数据结构:4.各种排序总结


前言

排序是将一组元素,重新排列成按关键字有序的序列。

  • 排序的稳定性:如果序列中有两个相同的关键字,排序之后这两个关键字的顺序没有发生变化,说明该排序是稳定的,否则排序是不稳定的。
  • 内部排序和外部排序:待排序列存放在计算机随机存储器中进行排序的过程(也就是内存中进行的排序)叫内部排序,如果待排的序列非常大,某一时刻只允许序列中的部分在内存中,需要借助外存来排序,叫外部排序

下面记录各种排序的实现,方便日后查阅。


1.插入排序

插入排序就是将一个元素插入到有序序列中,从而得到一个新的有序序列,适用于少量数据排序,时间复杂度为O(n^2),是稳定的排序方法。

1.1 直接插入排序

void insertSort(ElemType* arr, int len)
{
	int temp,j,i;
	for(i=1;i<len;i++)
	{
		temp = arr[i];
		j = i-1;
		while(j>=0 && temp<arr[j])
		{
			//只要没人temp大,向后腾位置
			arr[j+1] = arr[j];
			j--;
		}
		//多减了一次导致的while退出
		arr[j+1] = temp;
	}
}

1.2 折半插入排序

虽然也是插入排序,但是在找插入位置的时候使用折半查找。

void BinarySort(ElemType* array, int n)
{
	for(int i=1; i<n; i++)
	{
		int left=0;
		int right=i-1;
		int temp = array[i]
		while(left<=right)
		{
			int mid = (left+right)/2;
			if(temp>array[mid])
				left = mid+1;
			else
				right = mid-1;
		}
		for(int j=i;j>=left;j--)
			array[j] = array[j-1];
		array[left] = temp;
		
	}
}

1.3 希尔排序

希尔排序也是插入排序的一种,也称为缩小增量排序,是直接插入排序的一种更高效改进版本。希尔排序是一种非稳定排序算法,希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来愈多,当增量减少至1时,所有序列都被分为一组,排序结束。

void shellsort(ElemType*array, int len)
{
	int d = len / 2;   //分组(增量初始值)
	int i, j;
	int temp;
	while(d>0)          //取决于希尔排序要进行的次数(每一次都有相应的分组情况,即有不同的增值)
	{
		for(i = d; i<len; i++) //对该次希尔排序进行插入排序
		{
			temp = arr[i];
			for(j = i-d; j>=0 && temp<arr[j]; j = j-d)  //将每次的元素插入(由有序元素的个数来决定)
				arr[j+d] = arr[j];
		}
		d = d/2;  //重新分组
	}
}

3.冒泡排序

冒泡排序算是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就将他们交换过来。每一次都会找一个最大的,放到末尾。

冒泡排序改进:如果在某次的遍历中没有数据交换,说明整个数组已经有序。

void bulletSort(ElemType* arr, int len)
{
	int temp;
	for(int i=0; i<len-1; i++)
	{
		for(int j=0; j<len-1; j++)
		{
			if(arr[j]>arr[j+1])
			{
				temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}	
		}
	}
}

4. 快速排序

快速排序(QuickSort)是对冒泡排序的一种改进。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序的过程可以 递归进行,以此达到整个数据变成有序序列。

#include <stdio.h>

typedef int ElemType;

void quickSort(ElemType *arr, int left, int right)
{
	int i=left, j=right;
	int temp;
	if(left>=right) return;
	while(i<=j)
	{
		while(i<=j&&arr[left]>=arr[i]) ++i; //找出左边比arr[left]大的元素
		while(i<=j&&arr[left]<=arr[j]) --j; //找出右边比arr[left]小的元素

		if(i < j) //交换找到的元素
		{
			temp = arr[i];
			arr[i] = arr[j];
			arr[j] = temp;
			i++;
			--j;
		}
	}
	
	//经过循环后在j位置就是标杆的位置,这个位置左边都不大于该值,该位置右边都不小于该值
	temp = arr[left];
	arr[left] = arr[j];
	arr[j] = temp;
	quickSort(arr, left, j - 1);  //递归操作左边元素
	quickSort(arr, j+1, right);   //递归操作右边元素
}


int main()
{
    int arr[10] = {3,4,5,2,5,2,5,10,7,9};
    quickSort(arr, 0, 9);

    for(int i=0; i<10; i++)
        printf("%3d", arr[i]);

    printf("\n");

    return 0;
}

5.简单选择排序

思想:每一趟从n-i+1(i=1,2,…n-1)个记录中选择最小关键字作为有序序列中的第i个记录。

void selctSort(int *arr, int len)
{
    int temp;
    for(int i=0; i<len-1; i++)
    {
        for(int j = i + 1; j<len; ++j)
        {
            if(arr[i] > arr[j])
            {
                temp = arr[j];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }
    }
}

6.堆排序

堆的插入——每一次插入都是将新数据放在数组最后,而这个新数据的父节点到根节点必定是一个有序的数列,因此只要将这个新数据插入到这个有序数列中即可。

堆的删除——将最后一个数据的值赋给根节点,然后从根节点开始进行一次从上向下的调整。调整时先在左右儿子节点中找到最小的,如果父节点比这个最小的子节点还小说明不需要调整了,反之将父节点和它交换后再考虑后面的节点。相当于从根节点开始将一个数据在有序数列中进行“下沉”。

因此,堆的插入和排序非常类似直接插入排序,只不过实在二叉树上进行插入过程。

    //堆排序
    public static void heapSort(int[] arr) {
        //构造大根堆
        heapInsert(arr);
        int size = arr.length;
        while (size > 1) {
            //固定最大值
            swap(arr, 0, size - 1);
            size--;
            //构造大根堆
            heapify(arr, 0, size);
 
        }
 
    }
 
    //构造大根堆(通过新插入的数上升)
    public static void heapInsert(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            //当前插入的索引
            int currentIndex = i;
            //父结点索引
            int fatherIndex = (currentIndex - 1) / 2;
            //如果当前插入的值大于其父结点的值,则交换值,并且将索引指向父结点
            //然后继续和上面的父结点值比较,直到不大于父结点,则退出循环
            while (arr[currentIndex] > arr[fatherIndex]) {
                //交换当前结点与父结点的值
                swap(arr, currentIndex, fatherIndex);
                //将当前索引指向父索引
                currentIndex = fatherIndex;
                //重新计算当前索引的父索引
                fatherIndex = (currentIndex - 1) / 2;
            }
        }
    }
    //将剩余的数构造成大根堆(通过顶端的数下降)
    public static void heapify(int[] arr, int index, int size) {
        int left = 2 * index + 1;
        int right = 2 * index + 2;
        while (left < size) {
            int largestIndex;
            //判断孩子中较大的值的索引(要确保右孩子在size范围之内)
            if (arr[left] < arr[right] && right < size) {
                largestIndex = right;
            } else {
                largestIndex = left;
            }
            //比较父结点的值与孩子中较大的值,并确定最大值的索引
            if (arr[index] > arr[largestIndex]) {
                largestIndex = index;
            }
            //如果父结点索引是最大值的索引,那已经是大根堆了,则退出循环
            if (index == largestIndex) {
                break;
            }
            //父结点不是最大值,与孩子中较大的值交换
            swap(arr, largestIndex, index);
            //将索引指向孩子中较大的值的索引
            index = largestIndex;
            //重新计算交换之后的孩子的索引
            left = 2 * index + 1;
            right = 2 * index + 2;
        }
 
    }
    //交换数组中两个元素的值
    public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

7.归并排序

归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

void mergeInArr(ElemType arr[], int left, int mid, int right)
{
	//注意此时左右空间都是各自有序
	int length = right - left + 1; //辅助数组长度
	int *p = new int[length];      //构建辅助数组
	memset(p, 0, sizeof(int)*length);  //给辅助数组赋值
	int low = left;                    //记录左区间的起始位置
	int hig = mid + 1;                 //记录右区间的起始位置     
	int index = 0;                     //辅助空间下标
	while (low <= mid&&hig <= right) //左右区间都没有比较完,都有数据
	{
		while (low <= mid&&arr[low] <= arr[hig])//如果左边区间没有越界,并且左区间的数值<=右区间的数值
			p[index++] = arr[low++];            //将小的数据存入辅助空间
		while (hig <= right&&arr[low] > arr[hig]) //如果右边区间没有越界,并且左区间的数值>右区间的数值
			p[index++] = arr[hig++];   //将小的数据存入辅助空间
	}

	//到这一步,证明起码有一个区间是合并进了辅助数组
	if (hig <= right)//证明右区间并没有完成合并,左区间是完成合并,把右区间剩下的数据直接拷贝到辅助数组即可(此时右区间剩下的数据比辅助空间的数据大)
		memcpy(&p[index], &arr[hig], sizeof(int)*(right - hig + 1));
	if (low <= mid)
		memcpy(&p[index], &arr[low], sizeof(int)*(mid - low + 1));

	//将排完序的值传回给原数组
	memcpy(&arr[left], p, sizeof(int)*length); //这里&arr[left]要特别注意,不能写成arr
	delete[]p;

}

void merge(ElemType arr[], int left, int right)
{
	int mid;
	if (left >= right) return;   //递归出口   这里就不需要递归了
	mid = ((right - left) >> 1) + left;  //查找到中间值  将数组分成两部分 左区间和右区间

	//************  归操作  **********//
	merge(arr, left, mid);         //左区间     
	merge(arr, mid + 1, right);     //右区1间

	//************** 并操作 **************//
	mergeInArr(arr, left, mid, right);  //将归操作后单个有序区间合并

}

void mergeSort(ElemType arr[], int len)
{
	merge(arr, 0, len - 1);//归并排序
}


总结

排序图表:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值