数据结构(五)

排序
稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),称为非线性时间比较类排序。
非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。

按排序依据原则分类:
非线性时间比较类排序:(时间复杂度不能突破O(nlogn))
插入排序:直接插入排序、折半插入排序、希尔排序
交换排序:冒泡排序、快速排序
选择排序:简单选择排序、堆排序
归并排序:2-路归并排序
线性时间非比较类排序:
基数排序
桶排序
计数排序

1. 直接插入排序
将整个序列分为两个部分,一个是已排序序列(最开始将第一个元素放在已排序序列中),另外一个是待排序序列;每次将待排序序列中的一个元素插入到已排序序列中,直到所有的元素都在已排序序列中为止
//直接插入排序
void insertSort(int data[], int n)
{
    //在最开始的时候,认为data[0]是有序序列,从data[1]到data[n-1]是待排序序列;直接插入排序就是需要将data[1]开始的所有元素都出入到已排序序列中
    int i, j;

    for(i = 1; i < n; i++)
    {
        //每一趟是将待排序序列中的一个元素和已排序序列中的所有元素进行比较确定其插入位置
        for(j = i - 1; j >= 0; j--)  //循环执行完成以后,j的就是插入位置的索引
        {
            if(data[j] < data[i])
            {
                break;
            }
        }
        int t = data[i];
        //从插入位置j到有序序列的最后一个元素都要向后移动一个位置
        if(j != i - 1)
        {
            int k = i - 1;
            for(; k > j; k--)
            {
                data[k + 1] = data[k];
            }
        }

        //插入:在已排序序列中,第一个比待插入元素小的位置的后面一个位置就是插入位置
        data[j + 1] = t;
    }
}
PS E:\LinuxShared\vscode\cpp_source\数据结构202308\DS_8 排序算法> gcc .\排序算法.c -o test
PS E:\LinuxShared\vscode\cpp_source\数据结构202308\DS_8 排序算法> ./test
  13  27  38  49  65  76  97

直接插入排序的时间复杂度为O(n^2), 空间复杂度O(1)

2. 折半插入排序
在直接插入排序中查找待排序元素的位置采用的是顺序查找;如果采用折半查找,就成为折半插入排序
//折半插入排序
void binraryInsertSort(int data[], int n)
{
    int low, high, mid;
    int i, j, t;

    //折半插入排序和直接插入排序一样需要n-1趟插入的过程
    for(i = 1; i < n; i++)
    {
        //每一趟折半查找的起始区间: [0, i - 1]
        low = 0;
        high = i - 1;
        t = data[i]; //带排序的元素

        while(low <= high)
        {
            mid = (low + high) / 2;
            if(data[mid] > t)
            {
                high = mid - 1;
            }
            else
            {
                low = mid + 1;
            }
        }

        j = i;
        //插入位置是low,low后面的元素需要移动
        while(j > low && data[j - 1] > t)
        {
            data[j] = data[j - 1];
            j--;
        }
        //插入
        data[low] = t;
    }
}
时间复杂度:T(n)=O(n2)
空间复杂度:S(n)=O(1)

3. 希尔排序
排序过程:先取一个正整数d1<n,把所有相隔d1的记录放一组,组内进行直接插入排序;然后取d2<d1,重复上述分组和排序操作;直至di=1,即所有记录放进一个组中排序为止
希尔排序特点
子序列的构成不是简单的"逐段分割",而是将相隔某个增量的记录组成一个子序列
希尔排序可提高排序速度,因为
分组后n值减小,n2更小,而T(n)=O(n2),所以T(n)从总体上看是减小了
关键字较小的记录跳跃式前移,在进行最后一趟增量为1的插入排序时,序列已基本有序
增量序列取法
无除1以外的公因子
最后一个增量值必须为1

4. 冒泡排序
排序过程
将第一个记录的关键字与第二个记录的关键字进行比较,若为逆序r[1].key>r[2].key,则交换;然后比较第二个记录与第三个记录;依次类推,直至第n-1个记录和第n个记录比较为止——第一趟冒泡排序,结果关键字最大的记录被安置在最后一个记录上
对前n-1个记录进行第二趟冒泡排序,结果使关键字次大的记录被安置在第n-1个记录位置
重复上述过程,直到"在一趟排序过程中没有进行过交换记录的操作"为止


5. 快速排序
基本思想: 通过一趟排序,将待排序序列分隔为两个独立的部分,一部份是比关键字小的记录的集合,另外一个部分是都比关键字大的记录的集合;然后再分别对这两个部分进行排序,以达到整个序列有序
//快速排序
//第一步确定区轴(关键字)元素的位置, 对整个待排序序列按照区轴元素分隔为两个部分,一部分是比区轴元素小的元素的集合,另外一个部分是比区轴元素大的元素的集合;然后分别对两个部分进行排序
//第一步: 在数据区间data[low] ~ data[high]查找区轴元素的位置
int findPos(int data[], int low, int high)
{
    //先选择序列的第一个元素作为区轴元素(区轴元素的位置看作是一个坑)
    int t = data[low]; //第一个元素作为区轴元素(区轴元素所在的位置就是坑)
    while(low < high)
    {
        //首先从后往前找到第一个比区轴元素小的元素,将该元素填充到序列前半部分的坑中去
        while(data[high] >= t)
        {
            high--;
        }
        data[low] = data[high]; //填坑: 填充前半部分的坑,high位置就成为新的坑
        //然后从前往后找到第一个比区轴元素大的元素,将该元素填充到序列后半部分的坑中
        while(data[low] < t && low < high)
        {
            low++;
        }
        data[high] = data[low];
    }
    //区轴元素的位置为low==high的位置
    data[low] = t;  //确定了区轴元素在排序序列中的最终位置

    return low; //整个序列分隔为两个部分,low位置左边的都是比区轴元素小的,low位置右边的都是比区轴元素大的
}

void quickSort(int data[], int low, int high)
{
    //递归出口
    if(low > high)
    {
        return;
    }

    int pos = findPos(data, low, high);
    quickSort(data, low, pos - 1);  //对前半部分进行快速排序
    quickSort(data, pos + 1, high); //对后半部分进行快速排序
}

时间复杂度
最好情况(每次总是选到中间值作枢轴)T(n)=O(nlogn)
最坏情况(每次总是选到最小或最大元素作枢轴)T(n)=O(n2)
空间复杂度:需栈空间以实现递归
最坏情况:S(n)=O(n)
一般情况:S(n)=O(logn)

6. 简单选择排序
排序过程
首先通过n-1次关键字比较,从n个记录中找出关键字最小的记录,将它与第一个记录交换
再通过n-2次比较,从剩余的n-1个记录中找出关键字次小的记录,将它与第二个记录交换
重复上述操作,共进行n-1趟排序后,排序结束
时间复杂度
记录移动次数
   最好情况:0
   最坏情况:3(n-1)
T(n)=O(n2)

7. 堆排序
堆排序:将无序序列建成一个堆,得到关键字最小(或最大)的记录;输出堆顶的最小(大)值后,使剩余的n-1个元素重又建成一个堆,则可得到n个元素的次小值;重复执行,得到一个有序序列,这个过程叫~
建堆:
方法:从无序序列的第n/2个元素(即此无序序列对应的完全二叉树的最后一个非终端结点)起,至第一个元素止,进行反复筛选;
输出根元素以后恢复堆:
方法:输出堆顶元素之后,以堆中最后一个元素替代之;然后将根结点值与左、右子树的根结点值进行比较,并与其中小者进行交换;重复上述操作,直至叶子结点,将得到新的堆,称这个从堆顶至叶子的调整过程为"筛选"
时间复杂度:最坏情况下T(n)=O(nlogn)
空间复杂度:S(n)=O(1)

8. 归并排序
归并——将两个或两个以上的有序表组合成一个新的有序表,叫~
2-路归并排序
排序过程
设初始序列含有n个记录,则可看成n个有序的子序列,每个子序列长度为1
两两合并,得到n/2个长度为2或1的有序子序列
再两两合并,……如此重复,直至得到一个长度为n的有序序列为止
//2路归并
//合并另个序列: 将两个有序序列data[low]~data[mid]和data[mid + 1] ~ data[high]合并到一个序列中
void merge(int data[], int low, int mid, int high)
{
    //定义一个动态数组来存放data的两个有序的序列 -- 实际上就是定义一个动态数组用来存放数组中的所有的元素
    int *temp = (int *)malloc(sizeof(int) * (high - low + 1));
    //将整个动态数组分为两个序列
    int left_low = low;
    int left_high = mid;
    int right_low = mid + 1;
    int right_high = high;

    //将两个序列进行比较排序,小的排到前面
    int i, j;
    for(i = 0; left_low <= left_high && right_low <= right_high; i++)
    {
        if(data[left_low] <= data[right_low])
        {
            temp[i] = data[left_low++];
        }
        else
        {
            temp[i] = data[right_low++];
        }
    }

    //上面的for循环执行完成以后某个序列中可能还剩下有元素,需要手动将他们添加的动态序列的后面
    if(left_low <= left_high) //表示第一个部分至少还有一个元素
    {
        for(j = left_low; j <= left_high; j++)
        {
            temp[i++] = data[j];
        }
    }
    //如果第二个部分还有剩余的元素
    if(right_low <= right_high)
    {
        for(j = right_low; j <= right_high; j++)
        {
            temp[i++] = data[j];
        }
    }

    //将动态数组中的数据还原到data中
    for(i = 0; i < high - low + 1; i++)
    {
        data[low + i] = temp[i];
    }

    //释放动态内存
    free(temp);
    temp = NULL;

    return;
}

//归并排序
void mergeSort(int data[], int low, int high)
{
    int mid = 0;
    if(low < high)
    {
        mid = (low + high) / 2;
        //对左半部分进行归并排序
        mergeSort(data, low, mid);
        //对右半部分进行归并排序
        mergeSort(data, mid + 1, high);

        //合并
        merge(data, low, mid, high);
    }
}

#include <stdio.h>

void show(int data[], int n)
{
	int i;
	for(i = 0; i < n; i++)
	{
		printf("%4d ", data[i]);
	}
	printf("\n");
}

//直接插入排序
void insertSort(int data[], int n)
{

	int i, j;
	for(i = 1; i < n; i++)
	{
		//每一趟是将排序序列中的一个元素和已排序序列中的所有元素进行比较,确定其排序位置
		for(j = i - 1; j >= 0; j--)  //完成后,j为插入位置的索引
		{
			if(data[j] < data[i])  //不稳定,两个相同的数时,会改变相对位置.
			{
				break;
			}
		}
		int t = data[i];
		//从插入位置j到有序序列的最后一个元素都要向后移动一个位置
		if(j != i - 1)
		{
			int k = i - 1;
			for(; k > j; k--)
			{
				data[k+ 1] = data[k];
			}
		}
		//插入:在已排序序列中,第一个比待插入元素小的后面一个位置
		data[j + 1] = t;
	}
}

//折半插入排序
void binraryInsertSort(int data[], int n)
{
	int low, hight, mid;
	int i, j, t;
//折半插入排序和直接插入排序一样需要n-1趟插入的过程
	for(i = 1; i < n; i++)
	{
	  //每一趟折半查找的起始区间: [0, i - 1]
		low = 0;
		hight = i - 1;
		t = data[i]; //待排序的元素

		while(low <= hight)
		{
			mid = (low + hight) / 2;
			if(data[mid])
			{
				hight = mid - 1;
			}
			else
			{
				hight = mid + 1;
			}
		}
		j = i;
		//插入位置是low,low后面的元素需要移动
		while(j > low && data[j - 1] > t)
		{
			data[j] = data[j - 1];
			j--;
		}
		//插入
		data[j] 
	}
}

//快速排序:low,higt为区间
int findPos(int data[], int low, int hight)
{
	//先选择序列的第一个元素作为区轴元素(区轴元素的位置看作是一个坑)
	int t = data[low];
	while(low < hight)
	{
		//首先从后往前找到第一个比区轴元素小的元素,将该元素填充到序列前半部分的坑中去
		while(data[hight] >= t)
		{
			hight--;
		}
		data[low] = data[hight];  //填坑
		//然后从前往后找到第一个比区轴元素大的元素,将该元素填充到序列后半部分的坑中
		while(data[low] < t && low < hight)
		{
			low++;
		}
		data[hight] = data[low];
	}
	//区轴元素的位置为low==high的位置
	data[low] = t; //确定了区轴元素在排序序列中的最终位置
	return low; //整个序列分隔为两个部分,low位置左边的都是比区轴元素小的,low位置右边的都是比区轴元素大的
}

void quickSort(int data[], int low, int high)
{
	//递归出口
	if(low > high)
	{
		return;
	}
	
	int pos = findPos(data, low, high);
	quickSort(data, low, pos - 1); //对前半部分进行快速排序
	quickSort(data, pos + 1, high);  //对后半部分进行快速排序
}

//2路归并
void merge(int data[], int low, int mid, int high)
{
//定义一个动态数组来存放data的两个有序的序列 -- 实际上就是定义一个动态数组用来存放数组中的所有的元素
	int *temp = (int *)malloc(sizeof(int) *(high - low + 1));
	//将整个动态数组分为两个序列
	int left_low = low;
	int left_high = mid;
	int right_low = mid + 1;
	int right_high = high;

	//将两个序列进行比较排序,小的排到前面
	int i, j;
	for(i = 0; left_low <= left_high && right_low <= right_high; i++)
	{
		if(data[left_low] <= data[right_low])
		{
			temp[i] = data[left_low++];
		}
		else
		{
			temp[i] = data[right_low++];
		}
	}
	//上面的for循环执行完成以后某个序列中可能还剩下有元素,需要手动将他们添加的动态序列的后面
	if(left_low <= left_high) //表示第一个部分至少还有一个元素
	{
		for(j = left_low; j <= left_high; j++)
		{
			temp[i++] = data[j];
		}
	}
	if(right_low <= right_high) //如果第二个部分还有剩余的元素
	{
		for(j = right_low; j <= right_high; j++)
		{
			temp[i++] = data[j];
		}
	}
//将动态数组中的数据还原到data中
	for(i = 0; i < high - low + 1; i++)
	{
		data[low + i] = temp[i];
	}
//释放动态内存
	free(temp);
	temp = NULL;
	return;
}
void mergeSort(int data[], int low, int high)
{
	int mid = 0;
	if(low < high)
	{
		mid = (low + high) / 2;
		// //对左半部分进行归并排序
		mergeSort(data, low, mid);
		//对右半部分进行归并排序
		mergeSort(data, mid + 1, high);

		//合并
		mergeSort(data, low, high);
	}
}

int main()
{
	int arr[] = {49, 38, 65, 97, 76, 13, 27};
	int len = sizeof(arr) / sizeof(arr[0]);

	insertSort(arr, len); //直接插入排序
	show(arr, len);

	binraryInsertSort(arr, len); //折半插入排序
	show(arr, len);
	
	quickSort(arr, 0, len - 1); //快速排序
	show(arr, len);
	return 0;
}

9. 线性时间非比较类排序
桶排序、计数排序、基数排序这些排序算法的时间复杂度是线性的O(n),所以我们把这类排序算法叫作线性排序(Linear sort)
桶排序,顾名思义,会用到"桶",核心思想是将要排序的数据分到几个有序的桶里,每个桶里的数据再单独进行排序。桶内排完序之后,再把每个桶里的数据按照顺序依次取出,组成的序列就是有序的了。
桶排序是将n个数据分为m个部分,在每个部分中进行快速排序,时间复杂度是O(n);
在极端情况下,如果数据都被划分到一个桶里,那就退化为O(nlogn)的排序算法了。
桶排序比较适合用在外部排序中

计数排序: 当要排序的n个数据,所处的范围并不大的时候,比如最大值是k,我们就可以把数据划分成k个桶。每个桶内的数据值都是相同的,省掉了桶内排序的时间。桶内都是值相同的数据,所以并不需要再进行排序。我们只需要依次扫描每个桶,将桶内的数据依次输出到一个数组中,就实现了排序。因为只涉及扫描遍历操作,所以时间复杂度是O(n)。

#include <iostream>

using namespace std;

//计数排序,a是数组,n是数组大小。假设数组中存储的都是非负整数
void countingSort(int *a, int n)
{
	if(n <= 1)
		return;
// 查找数组中数据的范围 
	int i,max = a[0];
	for(i = 1; i < n; i++)
	{
		if(max < a[i])
		{
			max = a[i];
		}
	}
	int *c = new int[max + 1]; // 申请一个计数数组c,下标大小[0,max] 
	for(i 0; i <= max; i++)
	{
		c[i] = 0;
	}
// 计算每个元素的个数,放入c中
	for(i = 0; i < n; i++)
	{
		c[a[i]]++;
	}
	// 依次累加
	for(i = 1; i <= max; ++i)
	{
		c[i] = c[i -1] + c[i];
	}
	// 临时数组r,存储排序之后的结果
	int *r = new int[n]; // 计算排序的关键步骤,有点难理解
	for(i = n - 1; i >= 0; i--)
	{
		int index = c[a[i]] - 1;
		r[index] = a[i];
		c[a[i]]--;
	}
// 将结果拷贝给a数组
	for(i = 0; i < n; i++)
	{
		a[i] = r[i];
	}
}

int main()
{	
	int i,arr[] = {2, 5, 3, 0, 2, 3, 0, 3};
	countingSort(arr, 8);
	for(i = 0; i < 8; i++)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值