【数据结构】八大排序(超详解+附动图+源码)
目录
前言
常见排序算法的实现
1.插入排序
2.希尔排序
3.选择排序
4.堆排序
5.冒泡排序
6.快速排序
6.1 hoare版本
6.2挖坑法
6.3前后指针法
6.4快速排序优化
6.5快速排序非递归实现
7.归并排序
7.1递归实现
7.2非递归实现
8.计数排序(了解)
排序算法复杂度及稳定性分析
前言
所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
生活中各种地方都需要用到排序,所以学好排序算法是非常重要的。
排序分为 内部排序 和 外部排序。
内部排序:数据元素全部放在内存中的排序。
外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。
这部分主要是内部排序。排序讲解都以升序为例。
常见的排序算法:
// 排序实现的接口// 插入排序
void InsertSort(int* a, int n);// 希尔排序
void ShellSort(int* a, int n);// 选择排序
void SelectSort(int* a, int n);// 堆排序
void AdjustDwon(int* a, int n, int root);
void HeapSort(int* a, int n);// 冒泡排序
void BubbleSort(int* a, int n);// 快速排序递归实现// 快速排序hoare版本
int PartSort1(int* a, int left, int right);// 快速排序挖坑法
int PartSort2(int* a, int left, int right);// 快速排序前后指针法
int PartSort3(int* a, int left, int right);
void QuickSort(int* a, int left, int right);// 快速排序 非递归实现
void QuickSortNonR(int* a, int left, int right);// 归并排序递归实现
void MergeSort(int* a, int n);// 归并排序非递归实现
void MergeSortNonR(int* a, int n);// 计数排序
void CountSort(int* a, int n);
常见排序算法的实现
1.插入排序
思想:
把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。
实际中我们玩扑克牌时,就用了插入排序的思想:
演示过程动图:
单趟:若数组(arr)除最后一个元素外其余全部有序,设最后一个元素的下标为i,将arr[i]与前面的元素比较,前面的元素比他大则前面的元素向右移动,比他小则在该元素的后面插入。
复合:因为不知道数组中得到前几个元素是已经有序的,所以直接从第二个元素开始执行插入排序,单个过程与上述相同,将每个元素都进行一次插入排序。
代码如下:
// 插入排序
void InsertSort(int* a, int n)
{assert(a);int i = 0;for (i = 0; i < n - 1; i++)//因为x元素位置是i的下一个位置,为防止x越界,需要使 i < n-1{int end = i;//已经有序的最后一个元素(一个元素不需要排序,所以默认从0开始)int x = a[end + 1];//需要排序的元素//单趟while (end >= 0){//若前一个数字大于x,则需将他向右移动if (a[end] > x){a[end + 1] = a[end];//继续判断前面的元素--end;}//前面元素小于xelse{break;}}//将x插入正确位置(两种情况)//1.前面的数字小于x//2.前面的数字都大于x,x放在下标为0处a[end + 1] = x;}
}
直接插入排序的特性总结:
- 元素集合越接近有序,直接插入排序算法的时间效率越高
- 时间复杂度:O(N^2)
- 空间复杂度:O(1),它是一种稳定的排序算法
- 稳定性:稳定
2.希尔排序
希尔排序法又称缩小增量法。
基本思想:
先选定一个整数gap,把待排序文件中所有记录分成gap个组,所有距离为gap的记录分在同一组内,并对每一组内的元素进行排序。
然后将gap逐渐减小重复上述分组和排序的工作。
当到达gap=1时,所有元素在统一组内排好序。
静态图演示:
动图演示:
这里重要的是理解分组思想,每一个组其实就是一个插入排序,相当于进行多次插入排序。
代码如下:
// 希尔排序
void ShellSort(int* a, int n)
{assert(a);int gap = n;while (gap > 1){//gap /= 2;gap = gap / 3 + 1;for (int i = 0; i < n - gap; i++){int end = i;int x = a[end + gap];while (end >= 0){if (a[end] > x){a[end + gap] = a[end];end -= gap;}else{break;}}a[end + gap] = x;}}
}
希尔排序的特性总结:
- 希尔排序是对直接插入排序的优化。
- 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。
- 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,这里不深究。
- 时间复杂度O(N^1.5)
- 空间复杂度O(1)
- 稳定性:不稳定。
3.选择排序
基本思想:
第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始(末尾)位置,
然后选出次小(或次大)的一个元素,存放在最大(最小)元素的下一个位置,
重复这样的步骤直到全部待排序的数据元素排完 。
动图演示:
代码入下:
这里我们可以进行一个优化,最小值和最大值同时选,然后将最小值与起始位置交换,将最大值与末尾位置交换。
// 选择排序
void SelectSort(int* a, int n)
{assert(a);int begin = 0;//保存数组的起始位置int end = n - 1;//保存换数组的末尾位置while (begin < end){int maxi = begin;//保存最大元素下标int mini = begin;//保存最小元素下标//遍历数组寻找最小和最大元素for (int i = begin; i <= end; i++){if (a[i] < a[mini]){mini = i;}if (a[i] > a[maxi]){maxi = i;}}//将最小元素交换到起始位置Swap(a+begin, a+mini);//判断最大值的位置是否在起始位置if (maxi == begin){maxi = mini; }//将最大元素交换到末尾位置Swap(a+end, a+maxi);//移动数组起始和末尾位置begin++;end--;}
}
注意:
在进行最小值和最大值同时交换时也会出现一个问题,
如果最大值在起始位置的时候,交换了最小值之后,最大值就被交换到了min的位置,
如果继续交换max,就会将最小值交换到末尾位置。
所以,在每次交换了最小值之后应该判断一下最大值是否在起始位置,如果在需要将max赋值为min。
直接选择排序的特性总结:
- 直接选择排序思考非常好理解,但是效率不是很好(不论数组是否有序都会执行原步骤)。实际中很少使用
- 时间复杂度:O(N^2)
- 空间复杂度:O(1)
- 稳定性:不稳定
4.堆排序
堆排序即利用堆的思想来进行排序,总共分为两个步骤:
1. 建堆
- 升序:建大堆
- 降序:建小堆
2. 利用堆删除思想来进行排序
建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。
这里以升序为例:
- 首先应该建一个大堆,不能直接使用堆来实现。可以将需要排序的数组看作是一个堆,但需要将数组结构变成堆。
- 我们可以从堆从下往上的第二行最右边开始依次向下调整直到调整到堆顶,这样就可以将数组调整成一个堆,且如果建立的是大堆,堆顶元素为最大值。
- 然后按照堆删的思想将堆顶和堆底的数据交换,但不同的是这里不删除最后一个元素。
- 这样最大元素就在最后一个位置,然后从堆顶向下调整到倒数第二个元素,这样次大的元素就在堆顶,重复上述步骤直到只剩堆顶时停止。
动图演示:
注意:实际中并没有删除堆中元素,图中为了方便表示,将交换后的位置画成了空。
代码如下:
// 堆排序
void AdjustDown(int* a, int n, int root)//向下调整
{assert(a);int parent = root;int child = parent * 2 + 1;while (child < n){if (child + 1 < n && a[child + 1] > a[child]){child++;}if (a[child] > a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}void HeapSort(int* a, int n)
{assert(a);//建堆for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(a, n, i);}//交换for (int i = n - 1; i > 0; i--){Swap(&a[i], &a[0]);AdjustDown(a, i, 0);}
}
直接选择排序的特性总结:
- 堆排序使用堆来选数,效率就高了很多。
- 时间复杂度:O(N*logN)
- 空间复杂度:O(1)
- 稳定性:不稳定
5.冒泡排序
冒泡排序应该是我们最熟悉的排序了,在C语言阶段我们就学习了冒泡排序。
他的思想也非常简单:
两两元素相比,前一个比后一个大就交换,直到将最大的元素交换到末尾位置。这是第一趟
一共进行n-1趟这样的交换将可以把所有的元素排好。
(n-1趟是因为只剩两个元素时只需要一趟就可以完成)
动图演示:
代码如下:
// 冒泡排序
void BubbleSort(int* a, int n)
{assert(a);int i = 0;int flag = 0;//n-1趟排序for (i = 0; i < n-1; i++){int j = 0;//一趟冒泡排序for (j = 0; j < n - i - 1; j++){if (a[j] > a[j+1]){Swap(&a[j], &a[j+1]);flag = 1;}}//若某一趟排序中没有元素交换则说明所有元素已经有序,不需要再排序if (flag == 0){break;}}
}
冒泡排序的特性总结:
- 冒泡排序是一种非常容易理解的排序
- 时间复杂度:O(N^2)
- 空间复杂度:O(1)
- 稳定性:稳定
6.快速排序
这里是排序算法的重点了,非常重要!
快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,
其基本思想为:
任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
6.1 hoare版本
具体思路是:
- 选定一个基准值,最好选定最左边或者最右边,选中间会给自己找麻烦。
- 确定两个指针left 和right 分别从左边和右边向中间遍历数组。
- 如果选最右边为基准值,那么left指针先走,如果遇到大于基准值的数就停下来。
- 然后右边的指针再走,遇到小于基准值的数就停下来。
- 交换left和right指针对应位置的值。
- 重复以上步骤,直到left = right ,最后将基准值与left(right)位置的值交换。
这样基准值左边的所有数都比他小,而他右边的数都比他大,从而他所在的位置就是排序后的正确位置。
之后再递归排以基准值为界限的左右两个区间中的数,当区间中没有元素时,排序完成。
动图演示:
这里选定基准值为最右边的元素。
单趟图解:
继续按照上述步骤进行。。。
代码如下:
// 快速排序hoare版本
int PartSort1(int* a, int left, int right)
{int key = right;//选定基准值while (left < right){//选右边为基准值,左指针先走while (left < right && a[left] <= a[key]){left++;}//右指针再走while (left < right && a[right] >= a[key]){right--;}Swap(&a[left], &a[right]);}Swap(&a[left], &a[key]);return left;
}void QuickSort(int* a, int left, int right)
{assert(a);if (left >= right){return;}int keyi = PartSort1(a, left, right);QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);}
6.2挖坑法
挖坑法与上面的方法类似。
具体思路是:
- 先将选定的基准值(最左边)直接取出,然后留下一个坑,
- 当右指针遇到小于基准值的数时,直接将该值放入坑中,而右指针指向的位置形成新的坑位,
- 然后左指针遇到大于基准值的数时,将该值放入坑中,左指针指向的位置形成坑位,
- 重复该步骤,直到左右指针相等。最后将基准值放入坑位之中。
之后也是以基准值为界限,递归排序基准值左右区间。
动图演示:
这里不再单趟演示该过程。。。
代码如下:
// 快速排序挖坑法
int PartSort2(int* a, int left, int right)
{int key = a[left];//取出基准值int hole = left;//保存坑的位置while (left < right){while (left < right && a[right] >= key){right--;}a[hole] = a[right];hole = right;while (left < right && a[left] <= key){left++;}a[hole] = a[left];hole = left;}a[hole] = key;return hole;
}void QuickSort(int* a, int left, int right)
{assert(a);if (left >= right){return;}int keyi = PartSort2(a, left, right);QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);}
6.3前后指针法
前后指针法是一个新思路,不太好理解,但是代码比较简单。
具体思路是:
- 选定基准值,定义prev和cur指针(cur = prev + 1)
- cur先走,遇到小于基准值的数停下,然后将prev向后移动一个位置
- 将prev对应值与cur对应值交换
- 重复上面的步骤,直到cur走出数组范围
- 最后将基准值与prev对应位置交换
- 递归排序以基准值为界限的左右区间
动图演示:
单趟演示:
代码如下:
// 快速排序前后指针法
int PartSort3(int* a, int left, int right)
{//1.将基准值定在leftint keyi = left;int prev = left;int cur = left + 1;while (cur <= right){if (a[cur] < a[keyi] && ++prev != cur){Swap(&a[prev], &a[cur]);}cur++;}Swap(&a[prev], &a[keyi]);//2.将基准值定在right/*int keyi = right;int prev = left - 1;int cur = prev + 1;while (cur <= right){if (a[cur] < a[keyi] && ++prev != cur){Swap(&a[cur], &a[prev]);}cur++;}Swap(&a[keyi], &a[++prev]);return prev;*/
}void QuickSort(int* a, int left, int right)
{assert(a);if (left >= right){return;}int keyi = PartSort2(a, left, right);QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);
}
6.4快速排序优化
上面就是快速排序递归的三种方法。
但是上面的程序还有一些缺陷:
在基准值的选择上,如果选择的基准值为恰好为最小值,会进行不必要的递归。
在排序大量有序数据或者接近有序数据时,效率会比较低,甚至可能会出现程序崩溃的情况。
这是因为在排序有序数据时,快速排序的递归调用次数过多,会导致栈溢出的情况。
为了解决这些问题,这里有两种优化方法:
- 三数取中法选基准值
- 递归到小的子区间时,可以考虑使用插入排序
1.即在在起始位置,中间位置,末尾位置中选出中间值,作为基准值。
//三数取中int MidIndex(int* a, int left, int right)
{int mid = (left + right) / 2;//防止mid越界//int mid = left+(right - left) / 2;if (a[left] < a[right]){if (a[mid] < a[left]){return left;}else if (a[mid] > a[right]){return right;}else{return mid;}}else{if (a[mid] > a[left]){return left;}else if (a[mid] < a[right]){return right;}else{return mid;}}
}
2.类似于二叉树,每个子树都会进行一次递归调用,越到下面递归调用会越多。为了减少递归调用,当到递归到下层时,我们可以使用其他的排序来替代。这里我们使用插入排序。
以前后指针法为例,完整代码为:
// 快速排序前后指针法
int PartSort3(int* a, int left, int right)
{int mid = MidIndex(a, left, right);//将基准位置调整至最左边Swap(&a[mid], &a[left]);//1.将基准值定在leftint keyi = left;int prev = left;int cur = left + 1;while (cur <= right){if (a[cur] < a[keyi] && ++prev != cur){Swap(&a[prev], &a[cur]);}cur++;}Swap(&a[prev], &a[keyi]);//2.将基准值定在right/*int keyi = right;int prev = left - 1;int cur = prev + 1;while (cur <= right){if (a[cur] < a[keyi] && ++prev != cur){Swap(&a[cur], &a[prev]);}cur++;}Swap(&a[keyi], &a[++prev]);return prev;*/
}void QuickSort(int* a, int left, int right)
{assert(a);if (left >= right){return;}//小区间优化,,减少递归次数if (right - left + 1 < 10){InsertSort(a + left, right -left + 1);}else{int keyi = PartSort3(a, left, right);QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);}}
6.5快速排序非递归实现
快速排序非递归实现,需要借助栈,栈中存放的是需要排序的左右区间。
而且非递归可以彻底解决栈溢出的问题。
其实他的思想与递归是类似的:
具体思想:
- 将数组左右下标入栈,
- 若栈不为空,两次取出栈顶元素,分别为闭区间的左右界限
- 将区间中的元素按照前后指针法排序(其余两种也可)得到基准值的位置
- 再以基准值为界限,若基准值左右区间中有元素,则将区间入栈
- 重复上述步骤直到栈为空
代码如下:
// 快速排序 非递归实现
void QuickSortNonR(int* a, int left, int right)
{//创建栈Stack st;StackInit(&st);//原始数组区间入栈StackPush(&st, right);StackPush(&st, left);//将栈中区间排序while (!StackEmpty(&st)){//注意:如果right先入栈,栈顶为leftleft = StackTop(&st);StackPop(&st);right = StackTop(&st);StackPop(&st);//得到基准值int mid = PartSort3(a, left, right);// 以基准值为分割点,形成左右两部分if (right > mid+1){StackPush(&st, right);StackPush(&st, mid + 1);}if (left < mid - 1){StackPush(&st, mid - 1);StackPush(&st, left);}}StackDestroy(&st);
}
快速排序的特性总结:
- 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
- 时间复杂度:O(N*logN)
- 空间复杂度:O(logN)
- 稳定性:不稳定
7.归并排序
7.1递归实现
基本思想:
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。
将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。
若将两个有序表合并成一个有序表,称为二路归并。
归并排序核心步骤:
按照其算法思想:
首先应在数组中找出有序的序列,但数组是否有序编译器可不知道。
所以可以将数组划分,一分二,二分四......直到每个序列中只有一个数字。
一个数字总可以认为他是有序的吧。
最后将同时划分的序列合并。
动图演示:
代码如下:
void _MergeSort(int* a, int left, int right,int* tmp)
{//区间中没有元素时不再合并if (left >= right){return;}//划分数组,每次一分为二int mid = (left + right) / 2;_MergeSort(a, left, mid,tmp);//划分左区间_MergeSort(a, mid + 1, right,tmp);//划分右区间//合并有序序列int begin1 = left, end1 = mid;//有序序列1int begin2 = mid + 1, end2 = right;//有序序列2int i = left;while (begin1 <= end1 && begin2 <= end2)//注意结束条件为一个序列为空时就停止{if (a[begin1] < a[begin2]){tmp[i++] = a[begin1++];}else{tmp[i++] = a[begin2++];}}//两序列不可能同时为空,将剩余元素合并while (begin1 <= end1){tmp[i++] = a[begin1++];}while (begin2 <= end2){tmp[i++] = a[begin2++];}//将合并后的序列拷贝到原数组中//在这里拷贝的原因是 保证返回到上一层递归后两个子序列中的元素是有序的int j = 0;for (j = left; j <= right; j++){a[j] = tmp[j];}
}// 归并排序递归实现
void MergeSort(int* a, int n)
{assert(a);//因为需要将两个有序序列合并,需借助额外数组int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc");exit(-1);}_MergeSort(a, 0, n - 1,tmp);free(tmp);tmp = NULL;
}
7.2非递归实现
非递归实现的思想与递归实现的思想是类似的。
不同的是,这里的序列划分过程和递归是相反的,不是一次一分为二,而是先1个元素一组,再2个元素一组,4个元素一组....直到将所有的元素归并完。
静态图示:
代码如下:
// 归并排序非递归实现
void MergeSortNonR(int* a, int n)
{assert(a);int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc");exit(-1);}//初始化每组的元素个数为1int gap = 1;while (gap < n)//gap为n时就只有一个序列了,所以gap<n{//归并每两组归并一次int index = 0; //记录tmp数组中的元素下标for (i = 0; i < n; i+=2*gap)//两组中的元素个数为2*gap{//控制两组边界int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + 2 * gap - 1;//当原数组中元素个数不是2^n时,最后两组组会出现元素不匹配的情况//情况1:end1>=n或begin2>=n,即最后两组中只有一组有元素,则不需归并if (end1 >= n || begin2 >= n){break;}//情况2:end2>=n,即最后两组中,第二组元素个数小于第一组,则需要调整第二组边界if (end2 >= n){end2 = n - 1;}//归并while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){tmp[index++] = a[begin1++];}else{tmp[index++] = a[begin2++];}}while (begin1 <= end1){tmp[index++] = a[begin1++];}while (begin2 <= end2){tmp[index++] = a[begin2++];}}//一趟排完后,将归并后的有序序列拷贝到原数组中for (int j = 0; j < index; j++){a[j] = tmp[j];}//每次循环每组元素个数增大2倍gap *= 2;}free(tmp);tmp = NULL;
}
归并排序的特性总结:
- 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
- 时间复杂度:O(N*logN)
- 空间复杂度:O(N)
- 稳定性:稳定
8.计数排序(了解)
思想:计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。 操作步骤
1. 统计相同元素出现次数
2. 根据统计的结果将序列回收到原来的序列中
动图演示:
代码如下:
// 计数排序
void CountSort(int* a, int n)
{assert(a);// 创建计数数组,数组大小为原数组中最大值-最小值+1int max = a[0], min = a[0];int i = 0;for (i = 0; i < n; i++){if (a[i] > max){max = a[i];}if (a[i] < min){min = a[i];}}int range = max - min + 1;int* count = (int*)malloc(sizeof(int) * range);// 初始化计数数组为0memset(count, 0, range * 4);// 统计次数for (i = 0; i < n; i++){count[a[i] - min]++;}// 根据次数,进行排序int j = 0;for (i = 0; i < range; i++){while (count[i]--){a[j++] = i+min;}}free(count);count = NULL;
}
注意:计数排序在排负数时,可将负数的类型转化成 unsigned int。
数组中元素有正有负的情况时不适用计数排序。
计数排序的特性总结:
- 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。
- 时间复杂度:O(MAX(N,范围))
- 空间复杂度:O(范围)
- 稳定性:稳定
排序算法复杂度及稳定性分析
注:在特殊情况下,排序的时间复杂度会发生变化,不能作为判断该排序是否稳定的依据。
稳定性:指数组中相同元素在排序后相对位置不发生变化。
例如:在冒泡排序中,相同元素比较时不会交换顺序,从而排序后相同元素的相对位置不会发生变化,所以冒泡排序是稳定的。
最后我们对各种排序算法进行一个简单的测试:随机测试10000个数据
void TestOP()
{srand(time(0));const int N = 100000;int* a1 = (int*)malloc(sizeof(int) * N);int* a2 = (int*)malloc(sizeof(int) * N);int* a3 = (int*)malloc(sizeof(int) * N);int* a4 = (int*)malloc(sizeof(int) * N);int* a5 = (int*)malloc(sizeof(int) * N);int* a6 = (int*)malloc(sizeof(int) * N);for (int i = 0; i < N; ++i){a1[i] = rand();a2[i] = a1[i];a3[i] = a1[i];a4[i] = a1[i];a5[i] = a1[i];a6[i] = a1[i];}int begin1 = clock();InsertSort(a1, N);int end1 = clock();int begin2 = clock();ShellSort(a2, N);int end2 = clock();int begin3 = clock();SelectSort(a3, N);int end3 = clock();int begin4 = clock();HeapSort(a4, N);int end4 = clock();int begin5 = clock();QuickSort(a5, 0, N - 1);int end5 = clock();int begin6 = clock();MergeSort(a6, N);int end6 = clock();printf("InsertSort:%d\n", end1 - begin1);printf("ShellSort:%d\n", end2 - begin2);printf("SelectSort:%d\n", end3 - begin3);printf("HeapSort:%d\n", end4 - begin4);printf("QuickSort:%d\n", end5 - begin5);printf("MergeSort:%d\n", end6 - begin6);free(a1);free(a2);free(a3);free(a4);free(a5);free(a6);
}
如有错误,感谢指正~
来都来了,点个赞再走吧~~
【数据结构】八大排序(超详解+附动图+源码)相关推荐
- 备忘录模式详解 (附java语言源码)
备忘录模式(Memento Pattern): 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保持该状态,这样以后就可以将该对象恢复到保存的状态.(Without violating ...
- Java设计模式之原型模式最详解(类图+源码)
原型模式 原型模式概述:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象. 原型模式包含的角色 抽象原型类:规定了具体原型对象必须实现的clone()方法. 具体原型类 ...
- 算法 经典的八大排序算法详解和代码实现
算法 经典的八大排序算法详解和代码实现 排序算法的介绍 排序的分类 算法的时间复杂度 时间频度 示例 图表理解时间复杂度的特点 时间复杂度 常见的时间复杂度 空间复杂度 排序算法的时间复杂度 冒泡排序 ...
- 【数据结构与算法】详解什么是图结构,并用代码手动实现一个图结构
本系列文章[数据结构与算法]所有完整代码已上传 github,想要完整代码的小伙伴可以直接去那获取,可以的话欢迎点个Star哦~下面放上跳转链接 https://github.com/Lpyexplo ...
- 数据结构进阶 八大排序算法详解
数据结构就是定义出某种结构:像数组结构.链表结构.树形结构等,实现数据结构就是我们主动去管理增删查改的实现函数 排序的概念 所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列 ...
- C语言实现八大排序算法详解及其性能之间的
概述 排序是数据结构中的重要一节,也是算法的重要组成部分.主要分为内部排序以及外部排序,今天我们讲内部排序,也就是八大排序. 插入排序 直接插入排序 算法思想 算法图解 算法分析 算法实现 希尔排序 ...
- 排序算法总结(附动图解释和python实现)
排序算法总结(python实现) 目录 插入排序 直接插入排序(逐个插入到前面的有序子序中) 希尔排序 选择排序 直接选择排序(选择最小元放在前面) 堆排序(建堆不断取根节点调整堆) 交换排序 冒泡排 ...
- IOS 七种手势详解(动图+Demo下载)
原创Blog,转载请注明出处 blog.csdn.net/hello_hwc 欢迎关注我的博客专栏,这个关于IOS SDK的专栏我会持续更新 IOS SDK详解 前言: 触摸是交互的核心,而手 ...
- [十大排序]有的人图画着画着就疯了(1.5w字详细分析+动图+源码)
一个正经严肃的标题 啥是排序呢? 常见的排序算法及算法实现 1. 直接插入排序 复杂度及稳定性分析 2.希尔排序 复杂度及稳定性分析 3.选择排序 复杂度及稳定性分析 4.堆排序 复杂度及稳定性分析 ...
最新文章
- 分治策略解决幂乘问题
- 过来人的肺腑之言,攻读ML和CV硕士给我带来了什么?
- vb.net2019-下载文件
- 使用 Avalonia 开发 UOS 原生应用
- 剑指offer38题
- ibm java英语面试_IBMJava英文面试题(附参考答案)
- YFI 创始人 Andre Cronje:有四个新产品待发布,但担心在推出后被分叉
- Android中GsonFormat的安装及使用
- 沧小海笔记之PCIE协议解析——第一章 PCIE概述(上)
- idea 打包不出现target的原因
- Python GUI | 利用Tkinter制作签名设计软件!
- Elasticseach api keys are not enabled
- c++实现n阶行列式计算
- ThinkPad T61笔记本BIOS详细设置
- 橡皮擦的英语_小朋友们知道“橡皮擦”用英语该怎么说吗?
- 超级计算机阿波罗11,阿波罗11号制导计算机中指令模块和登月模块原始代码已在 GitHub 上开源...
- 解决windows10环境下conda创建环境出现 No writeable pkgs directories configured
- 无线耳机哪个品牌好?四大国内蓝牙耳机品牌排行
- QT下的几种透明效果
- win10防火墙打不开,设置是灰色的、edge闪退、应用商店灰色等问题