一、递归

一、什么是递归?

1.递归是一种非常高效、简洁的编码技巧,一种应用非常广泛的算法,比如DFS深度优先搜索、前中后序二叉树遍历等都是使用递归。
2.方法或函数调用自身的方式称为递归调用,调用称为递,返回称为归。
3.基本上,所有的递归问题都可以用递推公式来表示,比如
f(n) = f(n-1) + 1; 
f(n) = f(n-1) + f(n-2);
f(n)=n*f(n-1);

二、为什么使用递归?递归的优缺点?

1.优点:代码的表达力很强,写起来简洁。
2.缺点:空间复杂度高、有堆栈溢出风险、存在重复计算、过多的函数调用会耗时较多等问题。

三、什么样的问题可以用递归解决呢?

一个问题只要同时满足以下3个条件,就可以用递归来解决:
1.问题的解可以分解为几个子问题的解。何为子问题?就是数据规模更小的问题。
2.问题与子问题,除了数据规模不同,求解思路完全一样
3.存在递归终止条件

四、如何实现递归?

1.递归代码编写
写递归代码的关键就是找到如何将大问题分解为小问题的规律,并且基于此写出递推公式,然后再推敲终止条件,最后将递推公式和终止条件翻译成代码。
2.递归代码理解
对于递归代码,若试图想清楚整个递和归的过程,实际上是进入了一个思维误区。
那该如何理解递归代码呢?如果一个问题A可以分解为若干个子问题B、C、D,你可以假设子问题B、C、D已经解决。而且,你只需要思考问题A与子问题B、C、D两层之间的关系即可,不需要一层层往下思考子问题与子子问题,子子问题与子子子问题之间的关系。屏蔽掉递归细节,这样子理解起来就简单多了。
因此,理解递归代码,就把它抽象成一个递推公式,不用想一层层的调用关系,不要试图用人脑去分解递归的每个步骤。

递归的关键是终止条件
五、递归常见问题及解决方案

1.警惕堆栈溢出:可以声明一个全局变量来控制递归的深度,从而避免堆栈溢出。
2.警惕重复计算:通过某种数据结构来保存已经求解过的值,从而避免重复计算。

六、如何将递归改写为非递归代码?

笼统的讲,所有的递归代码都可以改写为迭代循环的非递归写法。如何做?抽象出递推公式、初始值和边界条件,然后用迭代循环实现。

二、排序

一、排序方法与复杂度归类
(1)几种最经典、最常用的排序方法:冒泡排序、插入排序、选择排序、快速排序、归并排序、计数排序、基数排序、桶排序。
(2)复杂度归类
O(n^2):    冒泡排序、插入排序、选择排序
O(nlogn): 快速排序、归并排序                   
O(n):        计数排序、基数排序、桶排序

二、如何分析一个“排序算法”?
<1>算法的执行效率
1. 最好、最坏、平均情况时间复杂度。
2. 时间复杂度的系数、常数和低阶。
3. 比较次数,交换(或移动)次数。
<2>排序算法的稳定性
1. 稳定性概念:如果待排序的序列中存在值相等的元素,经过排序之后,相等元素之间原有的先后顺序不变。
2. 稳定性重要性:可针对对象的多种属性进行有优先级的排序。
3. 举例:给电商交易系统中的“订单”排序,按照金额大小对订单数据排序,对于相同金额的订单以下单时间早晚排序。用稳定排序算法可简洁地解决。先按照下单时间给订单排序,排序完成后用稳定排序算法按照订单金额重新排序。
<3>排序算法的内存损耗
原地排序算法:特指空间复杂度是O(1)的排序算法。

常见的排序算法:

三、冒泡排序:时间复杂度O(n^2),空间复杂度O(1),稳定


冒泡排序只会操作相邻的两个数据。每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求,如果不满足就让它俩互换。

代码:

void BubbleSort(int arr[], int n)
{for (int i = 0; i < n - 1; i++){for (int j = 0; j < n - i - 1; j++){if (arr[j] > arr[j + 1]) {int temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}}}
}

四、插入排序:时间复杂度O(n^2),空间复杂度O(1),稳定

插入排序将数组数据分成已排序区间和未排序区间。初始已排序区间只有一个元素,即数组第一个元素。在未排序区间取出一个元素插入到已排序区间的合适位置,直到未排序区间为空。

代码:

//插入排序
void InsertionSort(int arr[], int size)
{int i;          //有序区间的最后一个元素的位置,i+1就是无序区间最左边元素的位置for(i = 0; i < size-1; ++i){int tmp = arr[i + 1];  //tmp是待插入到有序区间的元素,即无序区间最左边的元素int j = i;while(j >= 0 && tmp < arr[j]){        //寻找插入的位置 arr[j + 1] = arr[j];                //比tmp大的元素都往后移动 --j;}arr[j + 1] = tmp;}
}

五、选择排序:时间复杂度O(n^2),空间复杂度O(1),稳定


选择排序将数组分成已排序区间和未排序区间。初始已排序区间为空。每次从未排序区间中选出最小的元素插入已排序区间的末尾,直到未排序区间为空。


代码:

void selectionSort(int arr[],int n){for(int i=0;i<n;i++){//寻找[i,n)区间里的最小值int minIndex=i;        //为最小值的位置做个标记for(int j=i+1;j<n;j++)if(arr[j]<arr[minIndex])minIndex=j;swap(arr[i],arr[minIndex]);}}

六、归并排序

如果要排序一个数组,我们先把数组从中间分成前后两部分,然后对前后两部分分别排序,再将排好序的两部分合并在一起,这样整个数组就都有序了。

【算法逻辑】
归并的思路(分治)是把一个大问题a拆解成两个小问题b和c,解决了两个子问题再整合一下,就解决了原问题。用递归的方法,先分解再合并(分治是一种解决问题的处理思想,递归是一种编程技巧,这两者并不冲突):

实现思路:

merge-sort(p...r)表示,给下标从p到r之间的数组排序。我们将这个排序问题转化为了两个子问 ,题, merge_sort(p...q)和merge-sort(q+1..r),其中下标q等于p和r的中间位置,也就是, (p+r)/2,当下标从p到q和从q+1到r这两个子数组都排好序之后,我们再将两个有序的子数组合并在一起,这样下标从p到r之间的数据就也排好序了。

代码:

#include<iostream>using namespace std;void Merge(int arr[], int l, int q, int r){int n=r-l+1;//临时数组存合并后的有序序列int* tmp=new int[n];int i=0;int left=l;int right=q+1;while(left<=q && right<=r)tmp[i++] = arr[left]<= arr[right]?arr[left++]:arr[right++];while(left<=q)tmp[i++]=arr[left++];while(right<=r)tmp[i++]=arr[right++];for(int j=0;j<n;++j)arr[l+j]=tmp[j];delete [] tmp;//删掉堆区的内存
}void MergeSort(int arr[], int l, int r){if(l==r)return;  //递归基是让数组中的每个数单独成为长度为1的区间int q = (l + r)/2;MergeSort(arr, l, q);MergeSort(arr, q + 1, r);Merge(arr, l, q, r);}int main(){int a[8] = {3,1,2,4,5,8,7,6};MergeSort(a,0,7);for(int i=0;i<8;++i)cout<<a[i]<<" ";
}

快速排序

【算法逻辑】

从数列中挑出一个元素,称为 “基准”(pivot); 
重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作; 
递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

根据分治、递归的处理思想,我们可以用递归排序下标从 p 到 q-1 之间的数据和下标从 q+1 到 r 之间的数据,直到区间缩小为 1,就说明所有的数据都有序了。这里涉及到基准的选择问题,因此必须有函数Partition()来实现“基准”。

【代码实现】

#include<iostream>using namespace std;int Parition(int a[], int low,int high){int pivot=a[high];int i=low;for(int j=low;j<high;++j){//j指向当前遍历元素,如果大于等于pivot,继续向前//如果小于当前元素,则和i指向的元素交换if (a[j]<pivot) {swap(a[j], a[i]);i++;}}swap(a[i], a[high]);return i;}void QuickSort(int a[], int low, int high){if(low<high){int q=Parition(a,low, high);QuickSort(a, low, q-1);QuickSort(a, q+1,high);}}int main(){int a[8] = {3,1,2,4,5,8,7,6};QuickSort(a,0,7);for(int i=0;i<8;++i)cout<<a[i]<<" ";
}

八:线性排序:

时间复杂度O(n)

我们把时间复杂度是线性的排序算法叫作线性排序(Linear sort)常见的线性算法有: 桶排序、计数排序、基数排序

特点:

非基于比较的排序算法

桶排序

桶排序,顾名思义,会用到“桶" ,核心思想是将要排序的数据分到几个有序的桶里,每个桶里的数据再单独进行排序。桶内排完序之后,再把每个桶里的数据按照顺序依次取出,组成的序列就是有序的了。

对排序的数据要求苛刻:

1, 要排序的数据需要很容易就能划分成m个桶,并且,桶与桶之间有着天然的大小顺序。

2 ,数据在各个桶之间的分布是比较均匀的。

3 ,桶排序比较适合用在外部排序中。所谓的外部排序就是数据存储在外部磁盘中,数据量比较大,内存有限,无法将数据全部加载到内存中。

计数排序

计数排序只能用在数据范围不大的场景中,如果数据范围k比要排序的数据n大很多,就不适合用计数排序了。

计数排序只能给非负整数排序,如果要排序的数据是其他类型的,要将其在不改变相对大小的情况下,转化为非负整数。

代码:

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

散列表

什么是散列表:
散列表用的是数组支持按照下标随机访问数据的特性,所以散列表其实就是数组的一种扩展,由数组演化而来。可以说,如果没有数组,就没有散列表。

原理:
散列表用的就是数组支持按照下标随机访问的时候,时间复杂度是0(1)的特性。我们通过散列函数把元素的键值映射为下标,然后将数据存储在数组中对应下标的位置。当我们按照键值查询元素时,我们用同样的散列函数,将键值转化数组标标,从对应的数组下标的位置取数据。

散列函数的设计要求:
散列函数计算得到的散列值是一个非负整数;.
如果key1 = key2,那hash(key1) == hash(key2);
如果key1 != key2,那hash(key1)  !=  hash(key2),
散列函数的设计不能太复杂,散列函数生成值要尽可能随机并且均匀分布

如果不符合3 那么就出现了散列冲突,散列冲突是无法避免的

解决散列冲突的方法有两种: 
开放寻址法(open addressing)和链表法(chaining)

开放寻址法:如果出现了散列冲突,我们就重新探测一个空闲位置,将其插入。

装在因子:  散列表中一定比例的空闲槽位。公式: 散列表的装载因子 = 填入表中的元素个数 / 散列表的长度

装载因子越大,说明空闲位置越少,冲突越多,散列表的性能会下降。

链表法:

链表法是一种更加常用的散列冲突解决办法,相比开放寻址法,它要简单很多。我们来看这个图,在散列表中,每个"桶(bucket) "或者"槽(slot) "会对应一条链表,所有散列值相同的元素我们都放到相同槽位对应的链表中。

数据结构和算法详解(三)——递归、排序、散列表相关推荐

  1. 十大经典排序算法详解(三)-堆排序,计数排序,桶排序,基数排序

    养成习惯,先赞后看!!! 你的点赞与关注真的对我非常有帮助.如果可以的话,动动手指,一键三连吧!!! 十大经典排序算法-堆排序,计数排序,桶排序,基数排序 前言 这是十大经典排序算法详解的最后一篇了. ...

  2. 数据结构--图(Graph)详解(三)

    数据结构–图(Graph)详解(三) 文章目录 数据结构--图(Graph)详解(三) 一.深度优先生成树和广度优先生成树 1.铺垫 2.非连通图的生成森林 3.深度优先生成森林 4.广度优先生成森林 ...

  3. 数据结构与算法详解目录

    数据结构与算法详解是一本以实例和实践为主的图书,主要是经典的数据结构与常见算法案例,来自历年考研.软考等考题,有算法思路和完整的代码,最后提供了C语言调试技术的方法. 后续配套微课视频. 第0章  基 ...

  4. 二叉树遍历算法详解(递归法+非递归法)

    二叉树遍历算法详解 在上一篇C语言实现二叉树中有提到对于二叉树的遍历,包括前序,中序和后续遍历,以及层次遍历 大家已经熟悉了二叉树的前中后序遍历过程,大部分都采用了递归的思想来实现 在leetcode ...

  5. 数据结构与算法详解(含算法分析、动图图解、Java代码实现、注释解析)

    数据结构和算法的重要性 算法是程序的灵魂,优秀的程序可以在海量数据计算时,依然保持高速计算 数据结构和算法的关系: 程序 = 数据结构 + 算法 数据结构是算法的基础, 换言之,想要学好算法,需要把数 ...

  6. 数据结构——插入排序算法详解(C语言)

    插入排序的算法思想是:每一趟将一个待排序的记录,按其关键字的大小插入到已经排好序的一组记录的适合位置上,直到所有待排序记录全部插入为止. 例如,打扑克牌在抓牌时,每抓一张牌,就插入到合适的位置,直到抓 ...

  7. 《数据结构与算法》(二十)- 散列表查找

    目录 前言 1. 散列表查找(哈希表)概述 1.1 散列表查找定义 1.2 散列表查找步骤 2. 散列函数的构造方法 2.1 直接定址法 2.2 数字分析法 2.3 平方取中法 2.4 折叠法 2.5 ...

  8. 数据结构和算法详解(四)——五大基本算法思想

    更多例子可参考:https://blog.csdn.net/Aidam_Bo/article/details/86715865 一.穷举算法思想 穷 举 算 法 (ExhaustiveA ttack ...

  9. 数据结构和算法详解(二)——线性表(数组、链表、栈、队列)

    一.数组 线性表:   线性表就是数据排成像一条线一样的结构.每个现行表上的数据最多只有前和后两个方向.常见的线性表结构:数组,链表.队列.栈等. 什么是数组: 数组(Array)是一种线性表数据结构 ...

最新文章

  1. python网课一般多少钱-Python培训网课一般学费多少?毕业生能承担吗?
  2. list替换某一项需要遍历比对再set到对应的position
  3. 导入obj_3D模型obj文件格式详解
  4. 11 个 Linux 上最佳的图形化 Git 客户端
  5. Jexus 5.8.2 正式发布为Asp.Net Core进入生产环境提供平台支持
  6. 用Java解析:您可以使用的所有工具和库
  7. 数据库表命名 单数复数_数据是还是数据是? “数据”一词是单数还是复数?
  8. 如何将谷歌浏览器的背景色(包括显示的网站界面等)全部调为黑色?2020.12.28
  9. 【GPU编程】基于GPU的光线投射体绘制(GPU-Based Ray-Casting Volume Rendering)入门学习
  10. 删除某个路径下的文件夹
  11. [转]iOS SDK:iOS调试技巧
  12. 用一个小的例子来说明为什么TCP采用三次握手才能保证连接成功
  13. git原理和常用操作
  14. spring学习--引入外部文件,初始化属性
  15. Linux中的ls命令详细使用
  16. BT下载面临历史性转折
  17. 基于snowfall的玫瑰花瓣飘落效果
  18. python+django-mezzanine安装
  19. 基于java jsp的社区志愿者服务系统
  20. javaeye上对李刚的书的差评

热门文章

  1. 「懒惰的美德」我用 python 写了个自动生成给文档生成索引的脚本
  2. iOS - OC 与 Swift 互相操作
  3. 软件测试必读的七本书
  4. [实战]MVC5+EF6+MySql企业网盘实战(24)——视频列表
  5. 自动化测试中的测试执行自动化
  6. python pathos_python运行多线程库pathos时,pymongo递归深度溢出
  7. swift和java_Swift和Java关于字符串和字符的比较
  8. kafka 同步提交 异步_Kafka 位移提交那些事儿
  9. 程序员在编程中遇到的奇葩弱智问题(转)
  10. java webtable_java winform开发:JTable详解