算法学习总结(读书笔记)--Part1-排序算法总结

序算法总结

网上关于排序算法有很多,风格迥异,大家都有自己的习惯与方式。我也结合自己的风格,以及近期的找工作面试情况,试图用简单凝练的方式来记录它们,希望对于读者可以方便快速地掌握。
首先,需要把每种排序的定义记住~–此篇编码内容借鉴自liuyubobo老师

0)桶排序
即假设有11个桶,编号从0~10,每出现一个数,就在对应的编号的桶中放一个小旗子。这种类似桶排序的方法在实际中很好用,但是非常浪费空间,如果需要排序数的范围是0-210000000,那就得需要210000001个桶来存储。

1)冒泡排序(邻居好说话)
简单说就是:每次比较两个相邻的元素,如果它们的顺序错误就把它们交换过来。
定义:由第一个元素开始,比较相邻元素大小,若大小顺序有误,则对调。最坏与平均情况需要比较(n-1)+(n-2)+…+3+2+1 = n(n-1)/2 次。时间复杂度为O(n^2)。

/*冒泡排序*/
/*比较次数(n-1)+(n-2)+...+3+2+1 = n*(n-1)/2*/
void BubbleSort(int array[], int n)
{
	for (int i=n-1; i>0; i--)           //从n-1、n-2.....1(扫描次数)
	{
		for (int j = 0; j<i; j++)       //每一轮都能找到当前轮里面的最大值,之后就可以不比较这个最大值
		{
			if (array[j]>array[j+1])  //比较相邻的两个数值
			{
				swap(array,j,j+1);
			}
		}
	}
}

/*交换函数,作用是交换数组中的两个元素的位置*/
void swap(int array[], int i, int j)
{
	int tmp = array[i];
	array[i] = array[j];
	array[j] = tmp;
}

2)选择排序
定义:由第一个元素依次与后面的元素进行比较,顺序有误则交换。(冒泡是比较相邻的元素)最坏与平均情况需要比较(n-1)+(n-2)+…+3+2+1 = n(n-1)/2 次。时间复杂度为O(n2)。

/*选择排序*/
/*比较次数(n-1)+(n-2)+...+3+2+1 = n*(n-1)/2*/
void SelectionSort(int array[], int n){

    for (int i = 0 ; i < n ; i++) {
        int minIndex = i;                   //寻找[i, n)区间里的最小值,然后将最小值放到array[i],接着往后找极小值
		for (int j = i + 1 ; j < n ; j++) { //遍历当前轮次里的极小值
			if (array[j] < array[minIndex]) {
                minIndex = j;
			}
		}
        swap( array, i, minIndex);          //将最小值与i交换
    }

}

比较冒泡排序、选择排序与插入排序,同样无序的10000个数据所用时间比较

这里写图片描述
比较冒泡排序、选择排序与插入排序,同样有序的10000个数据所用时间比较
结果为:选择排序<插入排序<冒泡排序(根据所耗的时间)
这里写图片描述
可见,对于有序数据,插入排序效率提高很快。
结果为:插入排序<选择排序<冒泡排序(根据所耗的时间)

3)插入排序,逐一将数组中的元素与已排序好的元素进行比较,再将该数组元素插入到适当的位置。最坏与平均情况需要比较(n-1)+(n-2)+…+3+2+1 = n(n-1)/2 次。

/*-  6 4 9 8 3 -*/
void InsertSort(int array[], int n)
{
	for (int i = 1; i < n; i++)             //从位置1开始插入比较,位置0的数为第一次比较的数
	{
		for (int j = i; j > 0; j--)         //没错,就是 j-- ,每次最多需要比较 j 次
		{       
			if (array[j] < array[j-1]) {    //如果当前插入点小于前一个点,则交换,确保小的点在前面
				swap(array, j, j-1);
			}
			else {
			    break;                      //关键,也是优于选择和冒泡的地方:如果当前插入点大于前一个点,已经是正确的顺序了,此轮不用再排序
            }
		}
	}
}

在这里插入图片描述
4)希尔排序,类似于插入排序法,但它可以减少数据移动的次数。排序的原则是将数据划分成特定间隔的几个子集,以插入排序法排完子集内的数据后再逐渐减少间隔的距离。
这里写图片描述
这里写图片描述

/*希尔排序*/
/*步长依次伪 n/2,n/2/2,....*/
void ShellSort(int array[], int n)
{
	for (int delta = n/2; delta>0; delta /= 2)
	{
		for (int i = 0; i<delta; i++)
		{
			for (int j = i + delta; j<n; j += delta)
			{
				for (int k = j; k>0; k -= delta)
				{
					if (array[k]<array[k - 1])
						swap(array, k, k - 1);
				}
			}
		}
	}
}

5)快速排序,在数据中找到一个支点,把小于支点的数据放在左边而大于支点的数据放在右边,再以同样的方式分别处理左右两边的数据。

/*快速排序*/
int __partition(int array[], int l, int r){

    int v = array[l];           // 第一个元素设为v,分成后面的数,为 <v 和 >v 的数 

    int j = l;                  // j为区分小于和大于v的索引
    for ( int i = l + 1 ; i <= r ; i ++ ) { //从l+1开始遍历整个数组
        if ( array[i] < v ) {   // 当前元素arr[i]小于v,将当前元素arr[i],移动到arr[j+1]位置
            j++;
            swap(array, j, i);
        }
	}

    swap(array, l, j);          // arr[l]为起始位置,arr[j]为中间的key值

    return j;
}

// 对arr[l...r]部分进行快速排序,递归调用
void __quickSort(int arr[], int l, int r)
{
    if( l >= r )   //关键:递归判断条件
        return;

    int p = __partition(arr, l, r);//找中间的Key
    __quickSort(arr, l, p-1 );
    __quickSort(arr, p+1, r);
}

上面讲的 __partition 内容为下图所示,i指向当前元素,l为起始元素,j为Key值的位置
1)如果arr[i] > v, i接着往后走,不需要动
2)如果arr[i] < v, 这是需要将 arr[i] 的值交换到 j+1 的位置
3)最后需要将起始的 arr[l] 元素与 arr[j] 交换,使所有元素在 arr[l] 左边的都比它小,在它右边的都比它大,(因为 arr[l] 为 Key的数值,而 j 为其位置,需要将Key值放到“中间”来,所以需要做这次交换)
在这里插入图片描述

6)归并排序 O(logNN),因为二分后形成了O(logN)层级,再用O(N)算法来解决,所以出现了O(logNN)的算法复杂度。
这里写图片描述

/------------二叉树的遍历------------/
先来简单的递归调用的:

struct TreeNode 
{
     int val;
     TreeNode *left;
     TreeNode *right;
     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
// 前序 BAC(B为根)
void Preorder(TreeNode* ptr)
{
	if (ptr != NULL)
	{
		cout << ptr->val;
		Preorder(ptr->left);
		Preorder(ptr->right);
	}
}
// 中序 ABC(B为根)
void Inorder(TreeNode* ptr)
{
	if (ptr != NULL)
	{
		Inorder(ptr->left);
		cout << ptr->val;
		Inorder(ptr->right);
	}
}
// 后序 ACB(B为根)
void Postorder(TreeNode* ptr)
{
	if (ptr != NULL)
	{
		Postorder(ptr->left);
		Postorder(ptr->right);
		cout << ptr->val;
	}
}

2、最大公约数与最小公倍数求法

最大公约数用辗转相除法:取两个数中最大的数做除数,较小的数做被除数,用最大的数除较小数,如果余数为0,则较小数为这两个数的最大公约数,如果余数不为0,用较小数除上一步计算出的余数,直到余数为0,则这两个数的最大公约数为上一步的余数。

// 最大公约数
int gcd(int a,int b)
{ 
  int max = a > b ? a:b;
  int min = a < b ? a:b;//找最值
  int r = 0;            //余数
  while(max%min!=0)     //辗转相除法
  {
     r = max % min;
	 max = min;
	 min = r;
  }
  return min;
}

为什么可以用辗转相除法?

这里写图片描述
2、最小公倍数的求法
最小公倍数 = 两整数的乘积÷最大公约数
为啥?
因为,两整数的乘积,即为两数的公倍数,公倍数除以最大公约数,即为最小公倍数,换句话说,该公倍数除以最小公倍数,即为最大公约数。
所以

//最小公倍数
最小公倍数 == a*b/gcd(a,b);

3、原码、反码、补码

  1. 原码最高位表示符合位,剩下的位数,是这个数的绝对值得二进制
    int a = 10;
    10 的原码表示为 00000000 00000000 0000000 00001010
    -10的原码表示为 10000000 00000000 0000000 00001010

  2. 反码,正数的反码和其原码是一样的,负数的反码就是在其原码的基础上,符号位不变,其他位取反**。
    10 的反码表示为 00000000 0000000 00000000 00001010
    -10的反码表示为 11111111 1111111 11111111 11110101

  3. 补码,正数的补码就是原码,负数的补码为其反码的基础上+1
    10 的补码表示为 0000000 00000000 0000000 00001010
    -10的补码表示为 11111111 1111111 11111111 11111010
    –正数的原码、反码、补码都相等。

注:为什么要有原码、反码、补码?解决的是正负相加等于 “0”的问题
例如原码 1 表示为 0001,-1 表示为 1001,两者相加后为1010,计算机认为-2,因而出现了 反码:反码 1表示为 0001,-1 表示为 1110,两者相加后为 1111,1111 在反码中表示为 -0,满足要求,但是会出现两个零的存在 +0(0000) 与 -0(1111),因而引入了补码,从原来的反码的基础上加1,丢掉最高位后,完美地解决了 正负相加等于 “0”的问题,同时不会出现 +0 与 -0的不同表达。

4、0-1 背包问题


/*
   0-1背包问题,问题描述:有编号分别为a,b,c,d,e的五件物品,
   它们的重量分别是4,5,6,2,2,它们的价值分别是6,4,5,3,6,每件物品数量只有一个,
   现在给你个承重为 10 的背包,如何让背包里装入的物品具有最大的价值总和?
*/
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
using namespace std;
#define row    100
#define C 10
#define column 100
int f[row][column] = {0};
int W[5] = {4,5,6,2,2};
int V[5] = {6,4,5,3,6};
int main()
{
  //初始化1
  for(int i=0; i<=5; i++)
  {
    f[i][0] = 0;
  }
  //初始化2
  for(int j=1; j<=C; j++)
  {
	  f[0][j] = (j > W[0])? V[0]:0;
  }
  for(i=1; i<=5; i++)
  {
      for(int y=1; y<=C+1; y++)
	  {
	     if(y >= W[i])
		 {
			 f[i][y] = ((f[i-1][y] > (f[i-1][y-W[i]] + V[i])) ? f[i-1][y]:f[i-1][y-W[i]] + V[i]);
		 }
		 else
		 {
		     f[i][y] = f[i-1][y];
		 }
	  }
  }
  for(i=0; i<=5; i++)
  {
    for(j=0; j<=C+1; j++)
	{
	  cout<< f[i][j]<<" ";
	}
    cout<<endl;
  }
  return 0;
}

运行结果
状态方程 dp( j ) = Max( dp( j ), dp (j-w[i] ) + v[i] ),先放重量为4的物品,依次类推下去,比较价值与收益来决定是否将物体放入背包。

发布了19 篇原创文章 · 获赞 32 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/wangwangmoon_light/article/details/78187171