和左神一起做题:归并排序,稳定的冒泡,稳定的归并以及常见排序的空间时间稳定分析,数组到堆的过程以及演化成堆排序

//归并排序
#include<stdio.h>
void sort(int *a, int L, int M, int R)
{
	 int *p = new int[R - L + 1];
 	int p1 = L;
 	int p2 = M + 1;
 	int i = 0;
 	while (p1 <= M && p2 <= R)
 	{
  		p[i++] = a[p1] > a[p2] ? a[p1++] : a[p2++];
 	}
 	while (p1 <= M)
 	{
  		p[i++] = a[p1++];
 	}
 	while (p2 <= R)
 	{
  		p[i++] = a[p2++];
 	}
 	for (i = 0; i < R - L + 1; ++i)
 	{
  		a[L + i] = p[i];
 	}
}
void func(int *a, int L, int R)
{
 	if (L == R)
  		return;
 	int M = L + ((R - L) >> 1);
 	func(a, L, M);
 	func(a, M + 1, R);
 	sort(a, L, M, R);
}
int main()
{
 	int a[] = { 5, 8, 9, 6, 3, 4, 7, 2, 1, 0 };
 	func(a, 0, 9);
 	for (int i = 0; i < 10; ++i)
 	{
 		 printf("%d\n", a[i]);
 	}
 	return 0;
	}
//选择排序
#include<stdio.h>
#include<stdlib.h>
void swap(int *a, int *b)
{
	 int temp = *a;
	 *a = *b;
	 *b = temp;
}
int main()
{
	 int a[] = { 4, 5, 4, 6, 9, 9, 8, 7, 2, 2, 5, 6, 5 };
 	int c = sizeof(a) / sizeof(int);
	 int i;
 	int j;
	 for (i = 0; i < c - 1; i++)
	 {
  		int max = i;
  		for (j = i + 1; j < c; j++)
  		{
   			if (a[j] > a[max])
  			 {
    				max = j;
   			}
  		}
  	swap(&a[i], &a[max]);
 }
 	for (i = 0; i < c; ++i)
	 {
  		printf("%d", a[i]);
	 }
 	return 0;
}
//插入排序
#include<stdio.h>
void swap(int *a, int *b)
{
	int temp = *a;
	*a = *b;
	*b = temp;
}
int main()
{
	int a[] = { 4, 5, 4, 6, 9, 9, 8, 7, 2, 2, 5, 6, 5 };
	int c = sizeof(a) / sizeof(int);
	for (int i = 1; i < c; ++i)
	{
		for (int j = i; j>0 && a[j] > a[j - 1]; --j)
		{
			swap(&a[j], &a[j - 1]);
		}
	}
	for (int i = 0; i < c; ++i)
	{
	printf("%d", a[i]);
	}
	return 0;
}
#include<stdio.h>
void swap(int *a, int *b)
{
	 int temp = *a;
 	*a = *b;
 	*b = temp;
}
int main()
{
	 int a[] = { 4, 5, 4, 6, 9, 9, 8, 7, 2, 2, 5, 6, 5 };
 	int c = sizeof(a) / sizeof(int);
 	for (int i = 0; i < c - 1; ++i)
	{
 	 	for (int j = 0; j < c - 1 - i; ++j)
  		{
   			if (a[j]>a[j + 1])
   			{
    				swap(&a[j], &a[j + 1]);
   				if (a[j + 1] == a[j + 2])  //加上这一段就可以使冒泡排序变成稳定的排序
   				{
    					 j++;
    				}
   			}
 		 }
 	}
	 for (int i = 0; i < c; ++i)
 	{
 		 printf("%d", a[i]);
 	}
	 return 0;
}
插入排序:时间复杂度 o(N^2),空间复杂度o(1)
选择排序:时间复杂度 o(N^2),空间复杂度o(1)
冒泡排序:时间复杂度 o(N^2),空间复杂度o(1)
希尔排序:
归并排序:o(N*logN),空间复杂度o(N)
快速排序:o(N*logN),空间复杂度o(logN)
堆排序:o(N*logN),空间复杂度o(1)

在这里插入图片描述

递归函数就是,每次在调用子过程的时候就把父过程压入栈中(此时压入栈中的有已经有运行的行号以及参数,很多的详细信息),
当子过程完毕返回的时候,就又会把父过程从栈中弹出,这个过程是不需要我们自己实现的。
关于递归有一个master公式,可以直接求时间复杂度

在这里插入图片描述时间复杂度分三种情况可以自己查

关于归并排序的空间复杂度是o(N),其实空间复杂度是是可以o(1)的但是很复杂,属于论文级别的东西,感兴趣的可以搜索
一个归并排序的内部缓存法,在面试的场合就可以认为是o(N);
排序的稳定性指的是:
假如在一个无序序列在把他排为有序之前他会存在N个3,排为有序之后要是3的顺序还是第一个三在四二个三之前,也就是说N个
3的顺序没有改变,你那么就说这个排序是稳定的。 
快排的空间复杂度类似于二分查找的时间复杂度,都是o(logN),因为就算每次打到中间每次也许需要开辟新的空间来记录这个位
置,所以

在这里插入图片描述
8个元素的话就需要3个节点来变换记录中间的位置,但是拍好之后就会释放掉,这个是最优的解法

但是排好时候就返回的时候,就把内存释放了,所以最优也要o(logN)
最优的情况下空间复杂度为:O(logn)  ;每一次都平分数组的情况
最差的情况下空间复杂度为:O( n )      ;退化为冒泡排序的情况
综上所述:快速排序最差的情况下时间复杂度为:O( n^2 )
快速排序的平均时间复杂度也是:O(nlogn)

关于稳定性在这里讨论一下:
冒泡排序我们一般写的版本都不是稳定的版本,但是冒泡是可以写成稳定的版本的。
比如 7 5 7 4 7 3
冒的时候 7 5交换之后要是发现后面的元素和前面的相等,就用后面的元素去冒 
插入排序也是稳定的
选择排序是做不到稳定的
归并排序是可以做到稳定的
在于两个元素相等的时候拷的是左边还是右边,要是拷的是左边的话就可以保持稳定,也就在代码中再加一个=的事
p[i++] = a[p1] >= a[p2] ? a[p1++] : a[p2++];
快排(partition过程,分割)是不能做到稳定的 (但是如果你想吹逼的话就说又一篇论文叫做01 stable sort讲的就是
快排可以做到稳定,但是巨难),前提是空间复杂度为o(1),一般面试官问你这个题就是单纯不想要你。

还有一个面试官比较爱问的题目就是:一个无序的数组,让奇数在左,偶数在右,然后拍好之后就是间接的问快排如何做到稳定,这里我只是实现了分类没有实现顺序不变问题

#include<stdio.h>
void swap(int *a, int *b)
{
 	int temp = *a;
	 *a = *b;
 	*b = temp;
}
void func(int *a, int L, int R)
{
 	int less = L - 1;
 	int more = R;
	while (L < more)
	{
  		if ((a[L]) % 2 == 0)
  		{
   			less++;
   			L++;
  		}
  		else
  		{
   			swap(&a[L], &a[more--]);
  		}
 	}
}
int main()
{
 	int a[] = { 5, 7, 8, 9, 6, 3, 5, 4, 1, 2 };
 	func(a, 0, 9);
 	for (int i = 0; i < 10; ++i)
 	{
  		printf("%d ", a[i]);
 	}
 	return 0;
}
二叉树:落地其实就是数组,二叉树只是脑补出了来的画面,拿一个数组来说,一个元素的
		左孩子就是 2 * i  + 1
		右孩子就是 2 * i  + 2
 一个元素的父节点就是(2-1)/  2
 N个元素的完全二叉树的深度就是0(logN)
//将一个数组构造成为一个堆
#include<stdio.h>
void swap(int *a, int *b)
{
 	int temp = *a;
 	*a = *b;
	 *b = temp;
}
void heapInser(int *a, int i)
{
 	while (a[i] > a[(i - 1) / 2])   //我们这里构造的就是大堆
	 {
 	 swap(&a[i], &a[(i - 1) / 2]); //当子节点大于父节点的值的时候就一直while循环网往上换,直到找到合适的位置
  	i = (i - 1) / 2;   //这一步的目的就是上面一步 交换完之后万比交换后的位置的父节点还要大那么就要准备在while中做第二次循环了,所以要改变i的位置,确保交换的是同一个数字
 	}
}
void heapsort(int *a, int L)
{
	 if (a == NULL || L < 2)
 	{
 		 return;
	 }
 	for (int i = 0; i < L; i++)
 	{
 	 	heapInser(a, i);
	 }
}
int main()
{
 	int a[] = { 5, 0, 7, 6, 8 };
	 int L = sizeof(a) / sizeof(int);
 heapsort(a, L);
 printf("%d", a[0]);
 printf("%d", a[1]);
 printf("%d", a[2]);
 printf("%d", a[3]);
 printf("%d", a[4]);
 //printf("%d", a[5]);
 //printf("%d", a[6]);
 return 0;
}
#include<stdio.h>
void swap(int *a, int *b)
{
 	int temp = *a;
	*a = *b;
	*b = temp;
}
void heapInser(int *a, int i)
{
	 while (a[i] > a[(i - 1) / 2])      //如果是第一个元素那就是自己和自己交换不牵扯 
 	{
 		swap(&a[i], &a[(i - 1) / 2]); 
  		i = (i - 1) / 2;   
 	}
}
void heapify(int *a, int index, int size)
{
 	int left = index * 2 + 1;
 	//int largest = a[left] > a[left + 1] ? left : left + 1;
 	while (left < size)   //判断是不是存在左子树,要是存在的话才能进入循环
 	{
 		 int largest = left + 1 < size && a[left + 1] > a[left] ? left + 1 : left;//要确保这个largest是左右孩子里面的最大的 
 		 largest = a[largest]>a[index] ? largest : index;//这一步拿到的是三个元素中的最大者,也就是哦安定要不要进行再往下交换
 		 if (largest == index)
 		 {
   			break;
  		}
  		swap(&a[largest], &a[index]);
  		index = largest;   //交换下标,跟踪同一个元素
  		left = index * 2 + 1;    //确定下一个左孩子
 	}
}
void heapsort(int *a, int L)
{
 	int size = L;
 	if (a == NULL || L < 2)
 	{
 		return;
 	}
 	for (int i = 0; i < L; i++)
 	{
 		heapInser(a, i);
 	}
//=============================================================================//
 //接下来的操作就是在 上面已经将数组变成大堆的基础上进行排序
 	while (size > 0)
 	{
 		swap(&a[0], &a[--size]);
 		heapify(a, 0, size);
 	}
}
int main()
{
 	int a[] = { 5, 0, 7, 6, 8 };
 	int L = sizeof(a) / sizeof(int);
 	heapsort(a, L);
 	printf("%d", a[0]);
 	printf("%d", a[1]);
 	printf("%d", a[2]);
 	printf("%d", a[3]);
 	printf("%d", a[4]);
 	//printf("%d", a[5]);
 	//printf("%d", a[6]);
 	return 0;
}
堆排序存在两过程,一个是建堆,一个是排序,建堆的时候往上排,从下往上,排序的时候每次由于是把最后一个元素和堆顶交换
了,所以又是往下调整。
还有一点就是大堆排序出来的是由小到大,小堆排序出来是由大到小

在堆结构里找一个数比较快不是没有道理,因为每次只需要	和左右孩子中的一个进行比较交换就可以了,相当于每次只找一条路
径,所以次数就是你的深度。

n个数建立大根堆的时间复杂度就是o(log1)+o(log2)+o(log3)+o(log4)+...+o(logi)+...+o(logn),
在数学上合在一起是收敛的,所以才是o(logn)
每次插入的时候结构就已经是一个堆了,
插入第1个数就是把一个数插入0个元素的堆,o(log1)
插入第2个数就是把一个数插入1个元素的堆,o(log2)
插入第3个数就是把一个数插入2个元素的堆,o(log3)
插入第4个数就是把一个数插入3个元素的堆,o(log4)

在堆建立好之后,如果要排序就要把最后一个数和第一个数字交换,那么swap的时候时间复杂度就是0(1),然后在交换之后,堆顶元素往下走的时候,N个元素每个元素要 便利的深度都有可能是堆的深度,所以就是
0(NlogN).
总结
建堆:o(logn)
建堆完排序:0(N
logN)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值