//归并排序
#include
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
#include
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
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
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
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
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
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(NlogN)