一、分而治之的思想

  • 分而治之方法与软件设计的模块化方法非常相似
  • 分而治之通常不用于解决问题的小实例,而要解决一个问题的大实例。一般步骤为:
    • ①把一个大实例分为两个或多个更小的实例
    • ②分别解决每个小实例
    • ③把这些小实例的解组合成原始大实例的解

二、实际应用之找出假币

问题描述

  • 一个袋子有16个硬币,其中只有一个是假币,这个假币比其他的真币重量轻(其他所有真币的重量都是相同的),现在要找出这个假币

普通的解决方法

  • 一般的方法就是逐个的进行比较,一旦遇到质量比较轻的就找到假币了
  • 步骤为:
    • ①比较硬币1与硬币2,如果某个硬币比较轻,那么这个轻的硬币就是假币,终止寻找;否则进行下一步
    • ②继续比较硬币2与硬币3,如果某个硬币比较轻,那么这个轻的硬币就是假币,终止寻找;否则进行下一步
    • ......以此类推,直到找到假币终止寻找

分而治之的解决方法

  • 分而治之的思想是把一个问题的大实例分解为两个或更多的小实例,然后进行比较
  • 此处我们将大实例分解为两个小实例,步骤为:
    • ①把16个硬币分为两组A和B,每组8个硬币,计算每组硬币的总质量并进行比较,较轻的那一组肯定含有假币
    • ②将较轻的那一组继续分组,分为A和B,每组4个硬币,然后计算每组硬币的总质量并进行比较,同理,较轻的那一组肯定含有假币
    • ③将较轻的那一组继续分组,分为A和B,每组2个硬币,然后计算每组硬币的总质量并进行比较,同理,较轻的那一组肯定含有假币
    • ④最终只剩下两个硬币,因此不需要再进行分组了,直接比较,较轻的那一个硬币肯定是假币

三、实际应用之金块问题

问题描述

  • 一个老板有一袋金块,每块金块的重量都不同,现在想要找出最重的那个金块与最轻的那个金块

普通的解决方法

  • 普通的解决办法就是逐个比较,找出最重和最轻的金块
  • 步骤一般为:
    • 假设金块的总数为n
    • 先逐个比较每个金块,找出最重的金块
    • 找出最重的金块之后,从剩余的n-1个金块中再找出最轻的金块
  • 比较次数:因为找出最重的金块的比较次数为n-1次,从剩余的金块中再找出最轻的金块用了n-2次,所以总的比较次数为2n-3
template<typename T>
bool find_max_min(T arr[], int n,int &maxIndex,int &minIndex)
{if (n <= 0)return false;//先找出最大的maxIndex = 0;for (int i = 1; i < n; ++i) {if (arr[maxIndex] < arr[i])maxIndex = i;}//再从剩余的当中找出最小的minIndex = 0;for (int j = 1; j < n; j++) {if (j == maxIndex)continue;if (arr[minIndex] > arr[j])minIndex = j;}return true;
}int main()
{int arr[] = { 1,4,2,5,1,8,5 };int maxIndex, minIndex;if (find_max_min(arr, sizeof(arr) / sizeof(int), maxIndex, minIndex)) {std::cout << "max:" << arr[maxIndex] << endl;std::cout << "min:" << arr[minIndex] << endl;}return 0;
}

分而治之的解决方法

  • 当n<=2时,一次比较就足够了
  • 当n>2时,总体步骤如下:
    • ①将金块分为A和B两个部分
    • ②分别找出A和B中最重的和最轻的,设A中最重和最轻的金块分别为Ha和La,A中最重和最轻的金块分别为Hb和Lb(这一步可以使用递归来实现)
    • ③比较Ha与Hb就可以找出最重的,比较La与Lb就可以找出最轻的
  • 演示案例:
    • ①假设n=8,有8块金块
    • ②将8个金块分为两个部分A和B,各有4个金块
    • ③因为A中有4个金块,我们将其再分为两个部分A1和A2,每个部分有2个金块
      • 然后通过一次比较可以找出A1中较重的金块Ha1和La1
      • 再通过一次比较可以找出A2中较轻的金块Ha2和La2
      • 然后再通过一次比较Ha1与Ha2找出A中最重的金块Ha,通过一次比较La1与La2找出A中最轻的金块La
    • ④因为B中有4个金块,我们将其再分为两个部分B1和B2,每个部分有2个金块
      • 然后通过一次比较可以找出B1中较重的金块Hb1和Lb1
      • 再通过一次比较可以找出B2中较轻的金块Hb2和Lb2
      • 然后再通过一次比较Hb1与Hb2找出A中最重的金块Hb,通过一次比较Lb1与Lb2找出A中最轻的金块Lb
    • ⑤最后进行一次比较Ha和Hb找出最重的金块,再进行一次比较La和Lb找出最轻的金块。步骤结束
  • 可以看出在上面的分而治之中总共需要比较10次

  • 设c(n)为所需要的比较次数。为了方便,假设n是2的幂:

    • 如果是分而治之法:当n=2时,c(n)=1;对于较大的n,c(n)=2c(n/2)+2
    • 如果是逐个比较方法:c(n)=3n/2-2
    • 因此,使用分而治之方法比逐个比较的方法少用了25%的比较次数

分而治之编码实现

  • 如果使用递归,则步骤如下:

    • ①在上图的二叉树中,沿着根至叶子的路径,把一个大实例划分成为若干个大小为1或2的小实例
    • ②在每个大小为2的实例中,比较确定哪一个较重和哪一个较轻。在节点D、E、F、G完成这种比较。大小为1的实例只有一个金块,它既是最轻的也是最重的
    • ③对较轻的金块进行比较以确定哪一个最轻,对较重的金块进行比较以确定安一个最重。对节点A、B、C执行这种比较
  • 如果使用非递归的方法,代码如下:
    • 复杂度分析:当n为偶数时,在for循环外部又一次比较,内部有3(n/2-1)次比较。总的比较次数为3n/2。当n为奇数时,在for循环外部没有比较,内部有n(n-1)/2次比较。因此,无论n为奇数或偶数,当n>0时,总的比较次数为[3n/2]-2。这是在最早最大值最小值的算法中,比较次数最少的算法
template<typename T>
bool find_max_min(T arr[], int n,int &maxIndex,int &minIndex)
{//如果数组大小小于等于0,直接退出if (n <= 0)return false;//如果只有一个金块,那么它既是最重的也是最轻的if (n == 1) {maxIndex = minIndex = 0;return true;}//如果金块数量大于等于2,开始下面的部分int s = 0;//用于标识比较的开始起点if (n % 2 == 1) {//如果金块数量为奇数,设定最大索引与最小索引为0,然后从s=1索引处开始进行比较maxIndex = minIndex = 0;s = 1;}else {//如果金块数量为偶数,从前两个元素中提取出较小元素与较大元素的索引,然后从s=2索引处开始比较if (arr[0] > arr[1]) {maxIndex = 0;minIndex = 1;}else {maxIndex = 1;minIndex = 0;}s = 2;}//从s开始进行比较,每两个元素比较一次for (int index = s; index < n; index += 2) {if (arr[index] > arr[index +1]) {if (arr[index] > arr[maxIndex])maxIndex = index;if (arr[index + 1] < arr[minIndex])minIndex = index + 1;}else {if (arr[index] < arr[minIndex])minIndex = index;if (arr[index + 1] > arr[maxIndex])maxIndex = index + 1;}}return true;
}int main()
{int arr[] = { 1,4,2,5,0,1,8,3,8 };int maxIndex, minIndex;if (find_max_min(arr, sizeof(arr) / sizeof(int), maxIndex, minIndex)) {std::cout << "max:" << arr[maxIndex] << endl;std::cout << "min:" << arr[minIndex] << endl;}return 0;
}

四、实际应用之矩阵乘法

  • 待续

五、编码案例(残缺棋盘)

  • 待续

六、编码案例(归并排序)

  • 可以将分而治之的思想来设计排序算法,把n个元素按非递减顺序排列
  • 这种排序算法的思想是:
    • 若n为1,则算法终止
    • 否则,将序列划分为k个子序列(k是不小于2的整数),先对每一个子序列排序,然后将有序子序列归并为一个序列

二路划分

  • 假设将n元素的序列仅仅划分为两个子序列,称之为二路划分
  • 一种二路划分的方法:
    • 把前面n-1个元素放到第一个子序列中(称为A),最后一个元素放到第二个子序列中(称为B)。然后对A递归进行排序,然后再将B仅有的一个元素归并插入到A中
    • 这种方法是插入排序的递归形式。该算法的复杂度为O
  • 另一种二路划分的方法:
    • 将关键字最大的元素放入B,剩余元素放入A,然后对A进行递归排序。然后将A和B归并,此时直接将B添加到A的尾部就可以了

分而治之的方法

  • 上述方案是将n个元素划分为两个极不平衡的子序列A和B。A有n-1个元素,而B仅有一个元素
  • 现在我们划分的平衡一点,假设A包含n/k个元素,B包含其余的元素。递归地应用分而治之对A和B进行排序,然后采用一个被称之为归并的过程,将有序子序列A和B归并成一个序列
  • 假设有8个元素,关键字分别为[10、4、6、3、8、2、5、7]。则有:
    • 如果选定k=2:

      • 则可以划分出两个子序列[10、4、6、3]与[8、2、5、7]
      • 将上面两个子序列进行排序,可以得到两个有序子序列[3、4、6、10]、[2、5、7、8]
      • 现在从头元素开始比较,将两个有序子序列归并到一个子序列。元素2与3比较,2被移到归并序列;3与5比较,3被移动到归并序列;4与5比较,4被移到归并序列;5与6比较,以此类推....
    • 如果选定k=4:
      • 则可以划分出两个子序列[10、4]与[6、3、8、2、5、7]
      • 将上面两个子序列进行排序,可以得到两个有序子序列[4、10]、[2、3、5、6、7、8]
      • 根据相似的原理进行归并,便可以得到有序序列
  • 下面是对分而治之排序算法的伪代码描述。当生成的较小的实例个数为2,且A划分后的子序列具有n/k元素时,元素便可以得到最后结果:

  • 下面是相关的证明:

二路归并排序代码实现

  • 上面的伪代码是在k=2时的排序算法,称为归并排序,更准确的说,是二路归并排序
  • 下图是在k=2时的归并排序的C++函数(非完整版,简略版):
    • 用一个数组a存储元素序列E,并用a返回排序后的序列
    • 当序列E被划分为两个子序列时,不必把它们分别复制到A和B中,只需简单地记录它们在序列E中的左右边界
    • 然后将排序后的子序列归并到一个新数组b中,最后再将它们复制回a中

  • 如果仔细考察上面的程序,就会发现,递归只是简单地对序列反复划分,知道序列的长度变为1,这时再进行归并。这个过程用n为2的幂来描述会更好:

    • 长度为1的子序列被归并为长度为2的有序子序列
    • 长度为2的子序列被归并为长度为4的有序子序列
    • 这个过程不断重复,直到归并为一个长度为n的序列
    • 下图是n=8时的归并(和复制)过程

二路归并的迭代器算法(直接归并排序)

  • 有很多方面可以改进上面的C++函数。例如消除递归
  • 二路归并排序的一种迭代器算法是这样的:
    • 首先将每两个相邻的大小为1的子序列归并
    • 然后将每两个相邻的大小为2的子序列归并
    • 如此重复,直到只剩下一个有序序列
  • 轮流地将元素从a归并到b,从b归并到a,实际上消除了从b到a的复制过程,算法如下
  • 下面的函数用归并排序对数组元素a[0:n-1]排序
template<typename T>
void mergeSort(T a[], int n)
{T *b = new T[n];int segmentSize = 1;while (segmentSize < n){mergePass(a, b, n, segmentSize); //从a到b的归并segmentSize += segmentSize;mergePass(b, a, n, segmentSize); //从b到a的归并segmentSize += segmentSize;}delete[] b;
}
  • 为了完成排序代码,需要函数mergePass,不过这个函数仅用来确定需要归并的子序列的左右边界。实际的归并是由函数merge完成的
//从x到y归并相邻的数据段
template<typename T>
void mergePass(T x[], T y[], int n, int segmentSize)
{//下一个数据段的起点int i = 0;//从x到y归并相邻的数据段while (i <= n - 2 * segmentSize){merge(x, y, i, i + segmentSize - 1, i + 2 * segmentSize - 1);i = i + 2 * segmentSize;}//少于两个满数据段if (i + segmentSize < n)//剩余两个数据段merge(x, y, i, i + segmentSize - 1, n - 1);else//只剩一个数据段,复制到yfor (int j = i; j < n; j++)y[j] = x[j];
}//把两个相邻数据段从c归并到d
template<typename T>
void merge(T c[], T d[], int startOfFirst, int endOfFirst, int endOfSecond)
{int first = startOfFirst;    //第一个数据段的索引int second = endOfFirst + 1; //第二个数据段的索引int result = startOfFirst;   //归并数据段的索引//直到有一个数据段归并到归并段dwhile ((first <= endOfFirst) && (second <= endOfSecond)){if (c[first] <= c[second])d[result++] = c[first++];elsed[result++] = c[second++];}//归并剩余元素if (first > endOfFirst)for (int q = second; q <= endOfSecond; q++)d[result++] = c[q];elsefor (int q = first; q <= endOfFirst; q++)d[result++] = c[q];
}
  • 下面验证一下结果:

自然归并排序

  • 在自然归并排序中,首先认定在输入序列中已经存在的有序段
  • 例如:
    • 在输入数列[4、8、3、7、1、5、6、2]中可以认定4个有序段:[4、8],[3、7],[1、5、6],[2]
    • 从左至右扫描序列元素,若位置i的元素比位置i+1的元素大,则位置i便是一个分割点。然后归并这些有序段,直到剩下一个有序段
    • 归并有序段1和2可得有序段[3、4、7、8],归并有序段3和4可得有序段[1、2、5、6]。最后归并这两个有序段可得到[1、2、3、4、5、6、7、8]。这样,只需要两次归并
  • 自然归并的最好情况是:输入序列已经有序。自然归并排序只认定了一个有序段,不需要归并,但上面的mergeSort()函数仍要进行[]次归并,因此自然归并排序所需时间为Θ(n),而上面的mergeSort()函数需要用时Θ(n)
  • 自然归并的最坏情况是:输入序列按递减顺序排序。最初认定的有序段有n个。这是的归并排序和自然归并排序需要相同的归并次数,但自然归并排序为记录有序段的边界需要更多的时间。因此,在最坏情况下的性能,自然归并排序不如直接归并排序
  • 在一般情况下,n个元素序列有n/2个有序段,因为第i个元素关键字大于第i+1个元素关键字的概率是0.5。如果开始的有序段仅有n/2个,那么自然归并排序所需的归并比直接归并排序的要少。但是自然归并排序在认定初始有序段和记录有序段的边界时需要额外时间。因此,只有输入序列确实有很少的有序段时,才建议使用自然归并排序

七、编码案例(快速排序)

  • 基本思想为:

    • 把n个元素分为三段:左端left、中间段middle、右端right
    • 中间段仅有一个元素,左端的元素都不大于中间段的元素,右端的元素都不小于中间段的元素。因此可以对left段和right段独立排序,排序之后不用归并,直接就是有序的了
    • middle的元素称为“支点”或“分割元素”
  • 下面是快速排序的简单描述:

演示说明

  • 设有一个序列[4、8、3、7、1、5、6、2]:

    • 假设以元素6作为支点(middle),则left段包含4、3、1、5、2,right段包含8、7
    • 将left排序的结果为1、2、3、4、5,将right排序的结果为7、8
    • 最终得到有序序列[1、2、3、4、5、6、7、8]
  • 设有一个序列[4、3、1、5、2]:
    • 假设以元素3作为支点(middle),则left段包含1、2,right段包含4、5
    • 将left排序的结果为1、2,将right排序的结果为4、5
    • 最终得到有序序列[1、2、3、4、5]

C++代码实现

  • 下面是quick()的定义:

    • quickSort把数组的最大元素移动到数组的最右端,然后调用递归函数_qucikSort执行排序
template<typename T>
int indexOfMax(T a[], int n)
{int max = 0;int index = 0;for (int index = 1; index < n; index++){if (a[index] > a[max])max = index;}return max;
}template<typename T>
void quick(T a[], int n)
{if (n <= 1)return;//把最大元素移动到数组的最右端int max = indexOfMax<T>(a, n);std::swap(a[max], a[n - 1]);//然后调用_quickSort进行递归排序_quickSort(a, 0, n - 2);
}
  • quick()函数中为什么要将最大元素放置于最右边的原因:下面的_qucikSort()要求每一个数据段,或者其最大元素位于右端,或者其后继元素大于数据段的所有元素,因此,把最大元素移到最右端;如果这个条件不满足,例如,当支点是最大元素时,第一个do循环语句的结果是左索引值leftCursor将大于n-1
  • 下面是_quickSort()的定义:
    • _qucikSort把数据段划分为左、中、右支点(pivot)总是待排序数段的左元素。其实还可以选择性能更好的排序算法。在后面我们将讨论这种算法
    • 在do-while语句中,把关系操作符<和>改为<=和>=,程序依然正确(这时,数据段最右边的元素比支点要大)

template<typename T>
void _quickSort(T arr[], int leftEnd, int rightEnd)
{if (leftEnd >= rightEnd)return;int leftCursor = leftEnd;   // 记录该区间最左索引int rightCursor = rightEnd; // 记录该区间最右索引int pivot = arr[leftEnd];   // 我们以区间最左边的值为基准(哨兵)// 遍历整个区间while (leftCursor < rightCursor){// 从右向左找出一个比哨兵小的值, 其索引为rightCurosrwhile (leftCursor < rightCursor && arr[rightCursor] >= pivot)rightCursor--;// 然后将这个比哨兵小的值放到区间左边, 之后将leftCursor++// 备注, 此处不需要担心arr[leftCursor]的值是否找不到了, 因为我们已经用pivot将其记录下来了if(leftCursor < rightCursor)arr[leftCursor++] = arr[rightCursor];// 从左向右找出一个比哨兵大的值, 其索引为leftCursorwhile (leftCursor < rightCursor && arr[leftCursor] <= pivot)leftCursor++;// 然后比这个比哨兵大的值放到右边, 之后将rightCursorif(leftCursor < rightCursor)arr[rightCursor--] = arr[leftCursor];// 这一轮比较完了, 如果中间还有元素没有进行比较, 进行下一轮}// 把哨兵的值放到leftCursor所指的位置(中间), 因为此时leftCursor是arr[leftCursor] = pivot;_quickSort(arr, leftEnd, rightCursor - 1);  // 对左区间递归排序_quickSort(arr, rightCursor + 1, rightEnd); // 对右区间递归排序
}
  • 下面进行测试
int main()
{int a[] = { 4,5,1,4,2,3,8 };quick<int>(a, sizeof(a) / sizeof(int));for (auto val : a) {std::cout << val << " ";}return 0;
}

复杂度分析

  • 上面的quickSort()所需的递归栈空间为O(n)。若使用栈来模拟递归,则需要的空间可以减少为O(logn)。在模拟过程中,首先对数据段left和right中较小者进行排序,把较大者的边界放入栈中
  • 在最坏情况下,例如数据段left总是空,这时的快速排序用时为Θ()。在最好情况下,即数据段left和right的元素数目总是大致相同,这时的快速排序用时为O(nlogn)。而快速排序的平均复杂度也是Θ(nlogn)

  • 下图对本专栏的排序算法在平均情况下和最坏情况下的复杂度做了比较:

三值取中快速排序

性能测量

C++排序方法

八、编码案例(选择问题)

问题描述

  • 选择问题就是:从n个元素的数组中找出第k小的元素
  • 选择问题的一个实际应用就是寻找“中值”元素。此时k=[n/2]。例如查找中间工资、中间年龄等等

解决方法①

  • 选择问题可在O(nlogn)时间内解决。一种方法是:

    • 首先对n个元素的数组a进行排序(堆排序或归并排序等)
    • 然后取出a[k-1]的元素
  • 使用快速排序可以获得更好的平均性能。尽管该算法在最坏情况下的渐进复杂度比较差,仅为O()
  • 代码如下:
template<typename T>
T select(T a[], int n, int k)
{if (n < 1 || k < 1 || k > n)throw std::out_of_range("out_of_range");//排序,排序之后a为1 2 3 4 4 5 8std::sort(a, a + n);return a[k - 1];
}int main()
{int a[] = { 4,5,1,4,2,3,8 };int ret_val = select<int>(a, sizeof(a) / sizeof(int), 5);if (ret_val) std::cout << "select success:" << ret_val << std::endl;else std::cout << "select failed:" << ret_val << std::endl;return 0;
}

解决方法②

  • 修改上面的快速排序函数quickSort(),我们可以得到选择问题的一个较快的求解方法
  • 原理为:
    • 如果在两个while循环之后,将支点元素a[leftEnd]交换到a[j],那么a[leftEnd]便是a[leftEnd:rightEnd]中第j-leftEnd+1小的元素
    • 如果要寻找的第k小的元素在a[leftEnd:rightEnd]中:
      • 如果j-leftEnd+1等于k,那么答案就是a[leftEnd]
      • 如果j-leftEnd+1<k,那么要寻找的元素时right中的第k-j+leftEnd-1小的元素,否则是left中第k小的元素
    • 因此,只需进行0次或1次递归调用
  • 代码如下:在select中的递归调用可用for或while循环来代替
template<typename T>
int indexOfMax(T a[], int n)
{int max = 0;int index = 0;for (int index = 1; index < n; index++) {if (a[index] > a[max])max = index;}return max;
}template<typename T>
T select(T a[], int n, int k)
{if (k<1 || k>n)throw std::out_of_range("out_of_range");//把最大元素移动到数组的最右端int max = indexOfMax<T>(a, n);std::swap(a[max], a[n - 1]);//然后调用_quickSort进行递归排序return _select(a, 0, n - 1, k);
}template<typename T>
T _select(T a[], int leftEnd, int rightEnd, int k)
{if (leftEnd >= rightEnd)return a[leftEnd];int leftCursor = leftEnd, rightCursor = rightEnd + 1;T pivot = a[leftEnd];//将位于左侧不小于支点的元素和位于右侧不大于支点的元素交换while (true){do { //寻找左侧不小于支点的元素leftCursor++;} while (a[leftCursor] < pivot);do { //寻找右侧不大于支点的元素rightCursor--;} while (a[rightCursor] > pivot);//没有找到交换的元素对,退出if (leftCursor >= rightCursor)break;std::swap(a[leftCursor], a[rightCursor]);}if (rightCursor - leftEnd + 1 == k)return pivot;//放置支点a[leftEnd] = a[rightCursor];a[rightCursor] = pivot;//第一个数据段递归调用if (rightCursor - leftEnd + 1 < k)return _select(a, rightCursor + 1, rightEnd, k - rightCursor + leftEnd - 1);elsereturn _select(a, leftEnd, rightCursor - 1, k);
}int main()
{int a[] = { 4,5,1,4,2,3,8 };std::cout << "select success:" << select<int>(a, sizeof(a) / sizeof(int), 5) << std::endl;return 0;
}

复杂度分析

  • 解决方法②所用的程序在最坏情况下的复杂度是Θ()。所谓最坏情况就是left总是为空,第k小元素总是位于right。如果left和right总是一样大小或大小相差不超过一个元素,那么对上面的程序来说,可得到以下递归表达式:

九、编码案例(相距最近的点对)

  • 待续

十、分而治之解递归方程

解递归方程的方法

  • 解递归方程有若干技术:替代法、归纳法、特征根法和生成函数法
  • 本节描述一种查表法,可以用来求解许多与分而治之算法有关的递归方程

相关等式

十一、复杂度的下限

最小最大问题的下限

排序算法的下限

C++(数据结构与算法):60---分而治之、分而治之应用(残缺棋盘、归并排序、快速排序、选择问题、相距最近的点对)、分而治之解递归方程、复杂度的下限相关推荐

  1. 数据结构和算法系列5 七大排序之冒泡排序和快速排序

    排序是我们生活中经常会面对的问题.同学们做操时会按照从矮到高排列:老师查看上课出勤情况时,会按学生学号顺序点名:高考录取时,会按成绩总分降序依次录取等.排序是数据处理中经常使用的一种重要的运算,它在我 ...

  2. 数据结构与算法之--高级排序:shell排序和快速排序

    高级排序比简单排序要快的多,简单排序的时间复杂度是O(N^2),希尔(shell)排序大约是O(N*(logN)^2),而快速排序是O(N*logN). 说明:下面以int数组的从小到大排序为例. 希 ...

  3. 分治算法——分金块、求残缺棋盘问题

    一.分治算法的介绍 将整个问题分解成若干个小问题后分而治之.如果分解得到的子问题相对来说还太大,则可反复使用分治策略将这些子问题分成更小的同类型子问题,直至产生出方便求解的子问题,必要时逐步合并这些子 ...

  4. 数据结构与算法(二):堆,大根堆,小根堆,堆排序,比较器详解

    堆 什么是堆? 堆 是一棵 完全二叉树:即使它不是满二叉树,也是正在从左往右变满的过程中. 1)堆结构就是用数组实现的完全二叉树结构 2)完全二叉树中,如果每棵子树的 最大值都在顶部,是 大根堆 3) ...

  5. Java数据结构和算法(一)——简介

    本系列博客我们将学习数据结构和算法,为什么要学习数据结构和算法,这里我举个简单的例子. 编程好比是一辆汽车,而数据结构和算法是汽车内部的变速箱.一个开车的人不懂变速箱的原理也是能开车的,同理一个不懂数 ...

  6. 数据结构和算法(Java)-张晨光-专题视频课程

    数据结构和算法(Java)-579人已学习 课程介绍         如果说各种编程语言是程序员的招式,那么数据结构和算法就相当于程序员的内功. 想写出精炼.优秀的代码,不通过不断的锤炼,是很难做到的 ...

  7. 《数据结构与算法之美》学习汇总

    此篇文章是对自己学习这门课程的一个总结和课后的一些练习,做一个汇总,希望对大家有帮助.本人是半路程序员,2018年2月开始学习C++的,下面的代码基本都是C++11版本的,代码有错误的地方请不吝留言赐 ...

  8. 程序员的进阶课-架构师之路(1)-数据结构与算法简介

    现在市面上的数据结构与算法的教程也都不少,但有两个问题,第一是泛泛而谈,第二是基本都是c语言实现,而java作为第一主流语言,理应有它自己的独到之处.这也是我写这些博客的初衷,我会讲解java实现的数 ...

  9. java 算法_Java 浅谈数据结构和算法

    以前不管自己还是朋友在面试java工程师岗位的时候,都会被问到这样的问题: "介绍下java中的数据结构和算法". 很多朋友被问到的时候发现无从下口,甚至特别是一些初级java工程 ...

最新文章

  1. 用 Python 写个贪吃蛇,保姆级教程
  2. CSDN Github Markdown编辑常用功能符号补充
  3. 关于使用 jquery Validate 使用出现的问题
  4. 基于Knative开发应用
  5. Nginx 是如何实现高并发?常见的优化手段有哪些?
  6. 《Linux命令行与shell脚本编程大全 第3版》高级Shell脚本编程---35
  7. mybatis插入时间_深入分析MyBatis源码
  8. iPhone各机型屏幕尺寸
  9. 双线跟BGP线路区别
  10. 通俗理解电磁干扰及共模电感的原理
  11. ps绘画笔刷如何安装导入【一步步教你】
  12. Intellij idea 2020设置经典样式(背景为黑色Darcula)
  13. 大数据就业前景及职能定位解析!
  14. 如何使用 K8s 实现跨集群管理,这篇文章告诉你了!赶紧收藏
  15. html大于号是什么选择器,什么是“”(大于号)CSS选择器是什么意思?什么是“”(大于号)CSS选择器是什么意...
  16. 大四会计转码 初学c语言的心路历程
  17. BugkuCTF中套路满满的题--------never give up
  18. 【MNN学习六】基于Android的MNN编译安装
  19. 【AP】On the Bayesian interpretation of Black-Litterman(2)
  20. R统计笔记(二):投影运算与转换

热门文章

  1. android webservice 传递对象,Android通过ksoap2传递复杂数据类型及CXF发布的webservice详细介绍...
  2. NSIS(Nullsoft Scriptable Install System)开源安装程序制作软件使用详细步骤(Windows系统下)
  3. 微信小程序java后端开发记录(三):模板消息推送
  4. 2022 CCF BDCI 返乡发展人群预测 微弱提升(特征离散化)
  5. 简易的动物园管理系统之Java8流式API的重构代码(使用 状态设计模式)
  6. 渗透测试基础-文件上传漏洞 (下)
  7. “关键点法则”是一套关于简化生活、提高业绩和实现人生目标的公认体系
  8. git:‘remote-https‘ 不是一个 git 命令。参见 ‘git --help‘。
  9. TurtleBot机器人入门篇
  10. 苹果电脑能装鸿蒙,纯小白必看!鸿蒙编译及烧录环境分开部署For Mac