最近在学习清华邓俊辉老师的数据结构与算法,简单整理下。
Computer science should be called computing science,for the same reason why surgery is not called knife science.
一、 绪论
(随性而记的笔记)
1. 算法(信息处理)
算法是借助某种工具,遵照一定的规则,以明确而机械的形式进行
计算模型 = 计算机 = 信息处理工具
所谓算法,即特定计算模型下,旨在解决特定问题的指令序列。
输入(待处理的信息)、输出(经处理的信息)、正确性(可以解决指定的问题)、确定性(任一算法都可以描述为一个由基本操作组成的序列)、可行性(每一基本操作都可以实现,且在常数时间内完成)、有穷性(对于任何输入,经有穷次基本操作,都可以得到输出)
Hailstone序列的例子
#include <stdio.h>
#include <stdlib.h>
int hailstone(int n);
int main()
{
int n = 0;
int len = 0;
scanf("%d",&n);
len = hailstone(n);
printf("hailstone的长度是:%d",len);
return 0;
}
int hailstone(int n)
{
int length = 1;
while(n > 1){
(n%2) ? n = (3*n + 1) : (n = n/2);
length++;
}
return length;
}
一开始我编写这个程序时,在 (n%2) ? n = (3*n + 1) : (n = n/2); 这一语句,在问号的后面的表达式我没有添加括号(我实在codeblocks上运行该程序),然后会出现error: lvalue required as left operand of assignment(我想这应该是算数符号的优先级导致的)所以就改成上述的语句。所以这个提醒着我们加括号真的很重要啊!!!
冒泡排序
话不多说,直接上代码吧
#include <stdio.h>
#include <stdlib.h>
int main()
{
int A[] = {2, 3, 7, 6, 8, 1, 9};
int len = 0;
int i = 0;
int j = 0;
int temp = 0;
len = sizeof(A)/sizeof(int);
/*while(len-- > 0){
for(i = 0, j = 1;j < len; i++,j++){
if(A[i] > A[j]){
temp = A[i];
A[i] = A[j];
A[j] = temp;
}
}
}*/
for(i = 0; i < len; i++){
for(j = 0; j < len - 1 - i; j++){
if(A[j] > A[j+1]){
temp = A[j];
A[j] = A[j+1];
A[j+1] = temp;
}
}
}
for(i = 0; i < len; i++){
printf("%d\n",A[i]);
}
return 0;
}
一开始采用的是while循环,但是到最后打印数组时,我发现竟然没法打印数组,但是程序运行正常,没有报错。所以我开始打断点调试,一步一步的执行,最后发现程序中len在最后会变成0,随意没法打印数组。最后使用了for循环。(编程序还是要细心啊)当然这个程序也可以使用递归方法解决,利用减而治之的思想。
减而治之和分而治之
减而治之和分而治之是递归中两个很重要的思想。(接来下上图吧,盗用老师上课的PPT,私自截的图)
接下来当然是举例子了呀。
数组倒置,任给数组A[0,n),将其前后颠倒。
这个例子大家肯定很熟悉,一般我们采用的方法就是迭代
next:
if(lo < hi){
swap((A[lo],A[hi]));
lo++;
hi--;
goto next;
}
或是
while(lo < hi){
swap((A[lo],A[hi])); /*或是更精简的swap((A[lo++],A[hi--]));*/
lo++;
hi--;
}
我们也可以采用迭代方法来解决这个问题,采用减而治之的思想。
统一接口:
void reverse(int *A, int lo, int hi);
递归版:
if(lo < hi){
swap(A[lo], A[hi]);
reverse(A, lo + 1, hi - 1);
}
递归的思想就是先颠倒 A[lo] 和 A[hi],然后原数组中将这两个值从数组中剔除,形成了一个行的数组,然后采用同样的方法继续颠倒这个新数组的 A[lo] 和 A[hi],一直进行下去,直到 lo > hi ,结束递归。。。
在数组A[0,n]中找到最大的两个数值。
首先我想到的就是,冒泡排序法来寻找,因为是需要找到最大的两个。所以我的外循环只需两次,将最大的两个数放在数组的最后两位,代码如下:
int main()
{
int A[] = {2, 3, 7, 6, 8, 1, 9};
int len = 0;
int i = 0;
int j = 0;
int temp = 0;
len = sizeof(A)/sizeof(int);
for(i = 0; i < 2; i++){
for(j = 0; j < len - 1 - i; j++){
if(A[j] > A[j+1]){
temp = A[j];
A[j] = A[j+1];
A[j+1] = temp;
}
}
}
printf("%d\n%d\n",A[len-1],A[len-2]);
return 0;
}
这样就可以简单的实现寻找数组中最大的两个数。
接下来使用的是另一种迭代的方法(采用的C++方法)
void max2(int A[], int lo, int hi, int &x1, int &x2);
void swap(int *a, int *b);
int main()
{
int A[] = {2, 3, 7, 6, 8, 1, 9};
int lo = 0;
int hi = 0;
int lo1 = 0;
lo1 = lo + 1;
int & x1 = lo;
int & x2 = lo1;
hi = sizeof(A)/sizeof(int);
max2(A, lo, hi, x1, x2);
cout << A[x1] << endl;
cout << A[x2] << endl;
return 0;
}
void max2(int A[], int lo, int hi, int &x1, int &x2)
{
int i = 0;
if(A[x1] > A[x2]){
swap(x1,x2);
}
for(i = lo + 2; i < hi; i++){
if(A[x2] < A[i]){
if(A[x1] < A[x2 = i]){
swap(x1,x2);
}
}
}
}
void swap(int &a, int &b)
{
int temp = 0;
temp = a;
a = b;
b = temp;
}
假如在数组中最大的两个值刚好出现在数组的靠前部分,这样在迭代过程中可以减少很多计算,for循环中的第二个if语句就可以不用执行,相比较上一种迭代的方法,计算量就会少很多。
C++引用的用法见:https://blog.csdn.net/bzhxuexi/article/details/17588803
接下来就是“递归+分治”的思想,还是直接上代码吧
#include <iostream>
using namespace std;
void max2(int A[], int lo, int hi, int &x1, int &x2);
void swap(int *a, int *b);
int main()
{
int A[] = {45, 99, 2, 1, 2, 3, 7, 6, 8, 11, 8, 1, 9, 10};
/*int A[] = {2, 3, 7};*/
int lo = 0;
int hi = 0;
/*int lo1 = 0;
lo1 = lo + 1;*/
int x1 = 0;
int x2 = 0;
hi = sizeof(A)/sizeof(A[0]) - 1;
max2(A, lo, hi, x1, x2);
cout << A[x1] << endl;
cout << A[x2] << endl;
cout << hi << endl;
return 0;
}
void max2(int A[], int lo, int hi, int &x1, int &x2)
{
if(lo + 1 == hi){
if(A[x1 = lo] < A[x2 = hi]){
swap(x1,x2);
return;
}
return;
}
if(lo + 2 == hi){
if(A[x1 = lo] > A[x2 = lo + 1] ){
if(A[x1] > A[hi]){
if(A[x2] > A[hi]){
return;
}
else{
x2 = hi;
return;
}
}
else{
swap(x1,x2);
x1 = hi;
return;
}
}
else{
swap(x1,x2);
if(A[x1] > A[hi]){
if(A[x2] > A[hi]){
return;
}
else{
x2 = hi;
return;
}
}
else{
swap(x1,x2);
x1 = hi;
return;
}
}
}
int x1L;
int x2L;
int x1R;
int x2R;
int mi = 0;
mi = (lo + hi)/2;
max2(A, lo, mi, x1L, x2L);
max2(A, mi+1, hi, x1R, x2R);
if(A[x1L] > A[x1R]){
x1 = x1L;
x2 = (A[x2L] > A[x1R]) ? x2L : x1R;
}else{
x1 = x1R;
x2 = (A[x2R] > A[x1L]) ? x2R : x1L;
}
}
void swap(int *a, int *b)
{
int temp = 0;
temp = *a;
*a = *b;
*b = temp;
}
分而治之的思想就是将数组分成左右序列,分别求出左右序列中最大的两个数,假设左序列中的两个最大值是x1L和x2L(x1L >= x2L),有序列中的两个最大值是x1R和x2R(x1R >= x2R)。那么这个序列中的最大的两个值肯定就在这四个数值中。假如x1L大于x1R,那么最大值就是x1L,第二大数值肯定取(x2L和x1R中的最大值);假如x1R大于x1L,那么最大值就是x1R,第二大数值肯定取(x1L和x2R中的最大值)。划分的子序列可以继续划分下去,直至不能划分为止。说个题外话(本人是个菜鸡,真的很菜)开始编写代码时,代码出现了bug,起调试很久才发现是递归出口的地方出了错,但是我当时找了很久并没与发现是哪里出了错,最后我就想是瞎猫碰见死耗子一样发现是因为我在 (lo + 1 == hi) 这个条件时竟然忘记了返回另一种结果,就是忘记返回第二个 if 语句后面跟这个 else 情况,我记得上课时的时候老师强调过不要忘记 else 的那一部分。
接下来看另一个递归分-而治之思想的例子,归并排序
接下来直接上代码吧
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
void mergeSort(int* A, int low, int high);
void mergeArray(int* A, int low, int mid, int high);
int main()
{
int A[] = {6, 1, 2, 9, 7, 3};
int N = 0;
int i = 0;
N = sizeof(A)/sizeof(int) - 1;
mergeSort(A, 0, N);
for(i = 0; i<=N; i++){
printf("%d\n",A[i]);
}
return 0;
}
void mergeSort(int *A, int low, int high)
{
if(low >= high){
return;
}
int mid = 0;
mid = (low + high)/2;
mergeSort(A, low, mid);
mergeSort(A, mid + 1, high);
mergeArray(A, low, mid, high);
}
void mergeArray(int* A, int low, int mid, int high)
{
int i = 0;
int j = 0;
int k = 0;
int *p = (int*)malloc((high - low + 2)*sizeof(int));
i = low;
j = mid + 1;
while(i <= mid && j <= high){
if(A[i] < A[j]){
*(p + k) = A[i++];
k++;
}
else{
*(p + k) = A[j++];
k++;
}
}
while(i <= mid){
*(p + k) = A[i++];
k++;
}
while(j <= high){
*(p + k) = A[j++];
k++;
}
for(i = low, k = 0; i <= high; i++,k++){
A[i] = *(p + k);
}
free(p);
}
其实以前一直不是很理解关于递归的结束出口在哪里,该如何结束递归,借来上一张我自己理解的图吧,加深理解。以归并排序为例。
怎么图片上传不了,那就下次再上传吧。。。