【数据结构】排序的几种方法以及算法的稳定性、时间复杂度、空间复杂度的比较

时间复杂度与空间复杂度相关概念

1、时间复杂度

     时间复杂度可以认为是对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。

     常见的时间复杂度有:常数阶O(1),对数阶O(log2n),线性阶O(n), 线性对数阶O(nlog2n),平方阶O(n2)

     时间复杂度O(1):算法中语句执行次数为一个常数,则时间复杂度为O(1),

2、空间复杂度

    空间复杂度是指算法在计算机内执行时所需存储空间的度量,它也是问题规模n的函数

    空间复杂度O(1):当一个算法的空间复杂度为一个常量,即不随被处理数据量n的大小而改变时,可表示为O(1)

    空间复杂度O(log2N):当一个算法的空间复杂度与以2为底的n的对数成正比时,可表示为O(log2n)

                                 ax=N,则x=logaN,

    空间复杂度O(n):当一个算法的空间复杂度与n成线性比例关系时,可表示为0(n).

几种排序算法的稳定性、时间复杂度、空间复杂度的比较

 

1、直接插入排序

插入排序从小到大排序:首先位置1上的数和位置0上的数进行比较,如果位置1上的数大于位置0上的数,将位置0上的数向后移一位,将1插入到0位置,否则不处理。位置k上的数和之前的数依次进行比较,如果位置K上的数更大,将之前的数向后移位,最后将位置k上的数插入不满足条件点,反之不处理。

稳定性:稳定

时间复杂的:O(n^2)

空间复杂度:O(1)

若待排序列有序:时间复杂度为O(n)

#include <stdio.h>
void InsertSort(int *arr,int len)
{
	int i;
	int j;
	int tmp;
	for(i = 1;i < len ;i++)//找位置
	{
			tmp = arr[i];
			for(j = i - 1;j >= 0;j--)//从后向前找
			{
				if(arr[j] <= tmp)
				{
					break;
				}
				else
				{
					arr[j + 1]=arr[j];

				}
			}
			arr[j+1] = tmp;
	}
}

2、希尔排序

希尔排序是按照不同步长对元素进行插入排序,当刚开始元素很无序的时候,步长最大,所以插入排序的元素个数很少,速度很快;当元素基本有序了,步长很小, 插入排序对于有序的序列效率很高。所

稳定性:不稳定

时间复杂度:O(n^1.3~n^1.5)

空间复杂度:O(1)

 

void Shell(int *arr,int len ,int gap)//gap:为增量(间隔值数组)
{
	int i;
	int j;
	int tmp;
	for(i = gap;i <len ;i++)//
	{
		tmp = arr[i];
		for(j = i - gap;j >= 0;j -=gap )//同组前一个
		{
			if(arr[j] <= tmp)
			{
				break;
			}
			arr[j+gap] = arr[j];
		}
		arr[j + gap] = tmp;
	}
}
void ShellSort(int *arr,int len)
{
	int brr [] = {5,3,1};
	for (int i = 0;i < sizeof(brr)/sizeof brr[0];i++)
	{
		Shell(arr,len,brr[i]);
	}
}

3、冒泡排序

冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。

1、冒泡排序是一种用时间换空间的排序方法,n小时好
2、最坏情况是把顺序的排列变成逆序,或者把逆序的数列变成顺序,最差时间复杂度O(N^2)只是表示其操作次数的数量级
3、最好的情况是数据本来就有序,复杂度为O(n)

稳定性:稳定

时间复杂度:O(n^2)

空间复杂度:O(n)

将最大数沉底:

void BubbleSort(int *arr,int len)
{
	int i;
	int j;
	int tmp;
	/*for(i = 0;i < len - 1;i++)
	{
		for(j = 0;j+1 < len - i;j++)
		{
			if(arr[j] > arr[j + 1])
			{
				tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j +1] = tmp;
			}
		}
	}*/
	//进行优化
	bool flg;
	for(i = 0;i < len - 1;i++)
	{
		flg = false ;//为假,直接退出
		for(j = 0;j < len - 1;j++)
		{
			if(arr[j] > arr [j +1])
			{
				tmp = arr[j];
				arr[j] = arr[j+1];
				arr[j+1] = tmp;
				flg = true;//为真,交换
			}
		}
		if (!flg)
		{
			return ;
		}
	}
}

4、快速排序

快速排序从小到大排序:在数组中随机选一个数(默认数组首个元素),数组中小于等于此数的放在左边,大于此数的放在右边,再对数组两边递归调用快速排序,重复这个过程。

①递归实现

稳定性:不稳定

时间复杂度:O(nlogn)

空间复杂度:O(logn)

1、n大时好,快速排序比较占用内存,内存随n的增大而增大,但却是效率高不稳定的排序算法。
2、划分之后一边是一个,一边是n-1个,
这种极端情况的时间复杂度就是O(N^2)
3、最好的情况是每次都能均匀的划分序列,O(N*log2N)

快速排序一次划分,如果划分段<=1,则不再进行划分

static int partition(int *arr,int low,int high)
{
	int tmp = arr[low];//取一个数为基准
	while (low < high)
	{
		while  ((low < high) && (arr[high] >=tmp))
		{
			high --;
		}
		if(low == high)
		{
			break;
		}
		else
		{
			arr[high]=arr[low];
		}
	}
	arr[low] = tmp;
	return low;
}
static void Quick(int *arr,int low,int high)
{
	int par = partition (arr,low,high);
	if(low + 1 < par)
		Quick(arr,low,par-1);
	if(par < high - 1)
		Quick(arr,par+1,high);
}
//为了保持接口一样,用递归无法解决,所以添加此函数
void QuickSort(int *arr,int len)  
{
	Quick (arr,0,len-1);
}

②非递归实现

稳定性:不稳定

时间复杂度:O(n)

空间复杂度:O(logn)

void QuickSort2(int *arr,int len)
{
	Seqstack s;
	InitStack(&s);
	int low = 0;
	int high = len - 1;
	int par = partition(arr,low,high);
	if(low + 1 <par)//入栈
	{
		push (&s,low);
		push (&s,par - 1);
	}
	if(par +1 < high)
	{
		push(&s,par+1);
		push(&s,high);
	}
	while(!IsEmpty(&s))//出栈
	{
		pop(&s,&high);//取数据
		pop(&s,&low);
		par = partition(arr,low,high);
		if(low + 1 <par)//入栈
		{
			push (&s,low);
			push (&s,par - 1);
		}
		if(par +1 < high)
		{
			push(&s,par+1);
			push(&s,high);
		}
	}
	Destroy(&s);
}

 

5、选择排序

选择排序是给每个位置选择当前元素最小的,比如给第一个位置选择最小的,在剩余元素里面给第二个元素选择第二小的,依次类推,直到第n-1个元素,第n个 元素不用选择了,因为只剩下它一个最大的元素了。那么,在一趟选择,如果当前元素比一个元素小,而该小的元素又出现在一个和当前元素相等的元素后面,那么 交换后稳定性就被破坏了。

稳定性:不稳定

时间复杂度:O(n^2)

空间复杂度:O(1)

void SelectSort(int *arr,int len)
{
	int minIndex;
	int i;
	int j;
	int tmp;
	for(i = 0;i < len;i++)
	{
		minIndex  = i;
		for(j = i+1;j < len;j++)
		{
			if(arr[minIndex ] > arr[j])
			{
				minIndex =j;
			}
		}
		if(i !=minIndex )
		{
			tmp = arr[i ];
			arr[i] = arr[minIndex ];
			arr[minIndex ]=tmp;
		}
	}
}

6、归并排序

归并排序是把序列递归地分成短序列,递归出口是短序列只有1个元素(认为直接有序)或者2个序列(1次比较和交换),然后把各个有序的段序列合并成一个有 序的长序列,不断合并直到原序列全部排好序。

稳定性:稳定

时间复杂度:O(nlogn)

空间复杂度:O(n)

n大时好,归并比较占用内存,内存随n的增大而增大,但却是效率高且稳定的排序算法。

static void Merge(int *arr,int len,int gap)//gap:一个归并段的长度
{
	int low1 = 0;//第一个归并段的起始下标
	int high1 = low1+gap-1;//第一个归并段的结束下标
	int low2 = high1+1;//第二个归并段的起始下标
	int high2 = low2+gap<len?low2+gap-1:len-1;//第二个归并段的结束下标
	//额外创建数组用来存放数据
	int *brr = (int *)malloc (len*sizeof(int));
	int i= 0;//brr下标
	//判断有两个归并段
	while(low2 <len)
	{
		//①两个归并段都有数据
		while (low1 <= high1 && low2 <= high2 )
		{
			if(arr[low1] <=arr[low2])
			{
				brr[i++] = arr[low1++]; 
			}
			else
			{
				brr [i++] = arr[low2++];
			}
		}
		//②一个归并段已经没有数据,另一个还有
		while(low1 <=high1)
		{
			brr[i++] = arr[low1++];
		}
		while(low2 <= high2)
		{
			brr[i++] = arr[low2++];
		}
		//进入下两个归并段
		low1 = high2 + 1;
		high1 = low1 + gap -1;
		low2 = high1 + 1;
		high2 =  low2+gap<len?low2+gap-1:len-1;
	}
		//③只有一个归并段
		while(low1 < len)
		{
			brr[i++] = arr[low1 ++];
		}
		//将归并后的结果拷贝到arr中
		for (i =0; i<len;i++)
		{
			arr[i] = brr[i];
		}
		free(brr);
}
void MergeSort(int *arr,int len)
{
	for(int i= 1;i < len;i*=2)
	{
		Merge(arr,len,i);
	}
}

7、堆排序

堆排序从小到大排序:首先将数组元素建成大小为n的大顶堆,堆顶(数组第一个元素)是所有元素中的最大值,将堆顶元素和数组最后一个元素进行交换,再将除了最后一个数的n-1个元素建立成大顶堆,再将最大元素和数组倒数第二个元素进行交换,重复直至堆大小减为1。

稳定性:不稳定

时间复杂度:O(nlogn)

空间复杂度:O(1)

堆:大根堆、小根堆

 

//一次堆调整    O(logn)   O(1)
void HeapAdjust(int *arr,int start,int end)
{
	int tmp = arr[start];
	int parent = start;
	for(int i =2*start + 1;i <= end;i = 2*i +1)
	{
		//i保存左右孩子较大值的下标
		if(i+1 <=end && arr[i] < arr[i+1])
		{
			i++;
		}
		if (arr[i] > tmp)
		{
			arr[parent ] = arr[i];
			parent = i;
		}
		else
		{
			break;
		}
	}
	arr [parent ] = tmp;
}
//建立大根堆  O(nlogn)
void HeapSort(int *arr,int len)
{
	int i;
	int j;
	int tmp;
	for(i = (len - 1 -1)/2;i >= 0;i--)
	{
		HeapAdjust (arr,i,len-1);
	}
	for(i =0;i<len-1;i++)
	{
		tmp = arr[0];
		arr[0] = arr[len-1-i];
		arr[len-1-i] = tmp;
		HeapAdjust (arr,0,len-1-i-1);
	}
}

8、基数排序

稳定性:稳定

时间复杂度:O(d^n)

空间复杂度:O(n)

//用队列实现
//获取最大值位数
static int GetMaxFigure(int *arr,int len)
{
	int max = arr[0];
	int count = 0;
	for(int i = 0;i < len;i++)
	{
		if(max < arr[i])
		{
			max = arr[i];
		}
	}
	do 
	{
		count++;
		max /= 10;
	}while(max!=0);
	return count;
}
//得到十进制数字Num右数第figure位的值
//figure从0开始,如(123,0)->3
static int GetFigure (int num,int figure)
{
	for (int i =0;i < figure;i++)
	{
		num/=10;
	}
	return num%10;
}
//figure 为十进制数字右数第figure位
//figure从0开始
static void Radix(int *arr,int len ,int figure)
{
	HNode head[10];//创建队头数组
	int i;
	int index;
	for(i = 0;i  < 10;i++)
	{
		InitQueue(&head[i]);//初始化队头
	}
	for (i = 0;i <len;i++)//遍历数组
	{
		//按个位数将数放到对应的地方
		index  = GetFigure(arr[i],figure);
		push(&head[index],arr[i]);
	}
	int j=0;
	for(i = 0;i < len;)
	{
		if(pop(&head[j],&arr[i]))
		{
			i++;
		}
		else
		{
			j++;
		}
	}
}

void RadixSort(int *arr,int len)
{
	int count = GetMaxFigure (arr,len);
	for (int i = 0;i < count ;i++)
	{
		Radix (arr,len,i);
	}
}

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值