算法(Algorithm):一个计算过程,解决问题的方法

程序 = 数据结构+算法

时间复杂度:

当算法过程中出现循环折半的时候,复杂度式子中会出现 O(logn)

时间复杂度小结:
  1. 时间复杂度是用来估计算法运行时间的一个式子(是一个单位)
  2. 一般来说,时间复杂度高的算法比复杂度低的算法慢
  3. 常见的时间复杂度(按效率排序):
    O(1)<O(logn)<O(n)<O(nlogn)<O(n的平方)<O(n的平方logn)<O(n的立方)
  4. 复杂问题的时间复杂度:
    O(n!) O(2的n次方) O(n的n次方)

简单判断算法复杂度的方法:
快速判断算法复杂度(适用天绝大多数简单情况):
  -  确实问题规模n
  -  循环减半过程 -> logn
  -  k层关于n的循环 -> n的k次方
复杂情况:根据算法执行过程判断

空间复杂度

空间复杂度:用来 评估算法内存占用大小的式子空间复杂度的表示方式和时间复杂度完全一样:
  -  算法使用了几个变量:O(1)
  -  算法使用了长度为n的一维列表:O(n)
  -  算法使用了m行n列的二维列表:O(mn)
“空间换时间” (时间比空间重要)

递归:

递归的两个特点:
  -  调用自身
  -  结束条件

汉诺塔:

移动顺序:

  1. def hanoi(n,a,b,c): # n表示n个盘子,a、b、c表示从a经过b移动到c
  2. if n>0:
  3. hanoi(n-1,a,c,b)
  4. print("moving from %s to %s"%(a,c))
  5. hanoi(n-1,b,a,c)

移动次数:

  h(n)=2h(n-1)+1

查找:

查找:在一些数据元素中,通过一定的方法找出与给定关键字相同的数据元素的过程

列表查找(线性表查找):

线性查找: 从列表中查找指定元素

  - 输入:列表、待查找元素

  - 输出:元素下标(未找到元素时一般返回None或-1)
内置列表查找函数:index()

顺序查找:也称线性查找,从列表第一个元素开始,顺序进行搜索,直到找到元素或搜索到列表最后一个元素为止
时间复杂度:O(n)

代码:

  1. def linear_search(li,val):
  2. for ind,v in enumerate(li):
  3. if v == val:
  4. return ind
  5. else:
  6. return None

二分查找:

二分查找(Binary Search):又称折半查找,从【有序】列表的初始候选区li[0:n]开始,通过对待查找的值与候选区中间值的比较,可以使候选区减少一半
时间复杂度: O(logn)
代码:

  1. def binary_search(li,val):
  2. left = 0
  3. right = len(li)-1
  4. while left <= right: # 候选区有值
  5. mid = (left+right)//2
  6. if li[mid] == val:
  7. return mid
  8. elif li[mid] > val:
  9. right = mid - 1
  10. else:
  11. left = left+1
  12. else:
  13. return None

注:列表的内置函数index()用的是 线性查找

列表排序:

排序:将一组“无序”的记录序列调整为“有序”的记录序列
列表排序:将无序列表变为有序列表
  - 输入:列表
  - 输出:有序列表
升序和降序
内置排序函数:sort()

常见排序算法:

  1. 冒泡排序、选择排序、插入排序
  2. 快速排序、堆排序、归并排序
  3. 希尔排序、计数排序、基数排序

冒泡排序(Bubble Sort):

列表每两个相邻的数,如果前面比后面大,则交换这两个数
一趟排序完成后,则无序区减少一个数,有序区增加一个数
代码关键点:趟、无序区范围
时间复杂度:O(n的平方)
代码:

  1. def bubble_sort(li):
  2. for i in range(len(li)-i): # 第i趟
  3. exchange = False # 用于标识一趟中是否发生了位置互换
  4. for j in range(len(li)-1-i): # 指针移动
  5. if li[j]>li[j+1]: # 升序排序
  6. li[j],li[j+1] = li[j+1],li[j] # 交换位置
  7. exchange = True
  8. if not exchange: # 如果这一趟没有发生位置互换,意味着已经全部排序好了,下面的趟也就不用再进行了
  9. return

选择排序(Select Sort):

  一趟排序记录最小的数,放到第一个位置
  再一趟排序记录下列表无序区最小的数,放到第二个位置
  ...(以此类推)
算法关键点:有序区和无序区、无序区最小数的位置
时间复杂度:O(n的平方)
代码:

  1. def select_sort(li):
  2. for i in range(len(li)-1): # 第i趟
  3. min_loc = i # 记录最小值的位置(索引)
  4. for j in range(i+1,len(li)): # 查找无序区的最小值
  5. if li[j] < li[min_loc]:
  6. min_loc = j # 最小值的位置更改为j
  7. if i != min_loc:
  8. li[i],li[min_loc] = li[min_loc],li[i] # 互换位置

插入排序(Insert Sort):

初始时手里(有序区)只有一张牌,每次(从无序区)摸一张牌,插入到手里已有牌的正确位置
时间复杂度: O(n的平方)

代码:

  1. def insert_sort(li):
  2. for i in range(1,len(li)): # i表示抽出来的牌的下标
  3. temp = li[i]
  4. j = i - 1 # j 表示有序区牌的下标
  5. while j>=0 and li[j] > temp:
  6. li[j+1] = li[j] # 往右移
  7. j -= 1 # 下标左移
  8. li[j+1] = temp

快速排序:

快速排序:快
思路:
  -  取一个元素p(第一个元素),使元素p归位;
  -  列表被p分成两部分,左边都比p小,右边都比p大;
  -  递归完成排序

代码:

  1. def partition(li,left,right):
  2. """先让一个数归位(如左边第一个),然后把小于归位数的放到其左边,大于归位数的放到其右边"""
  3. temp = li[left]
  4. while left < right:
  5. while left < right and li[right] >= temp: # 从右边找比temp小的数
  6. right -= 1 # 这个数如果不比 temp小,则让 right 往左移一步
  7. li[left] = li[right] # 把右边的值写到左边空位上
  8. while left < right and li[left] <= temp:
  9. left += 1
  10. li[right] = li[left] # 把左边的值写到右边空位上
  11. li[left] = temp # 把temp归位 # 循环结束时, left==right
  12. return left
  13.  
  14. def quick_sort(li,left,right):
  15. if left < right: # 至少两个元素
  16. mid = partition(li,left,right)
  17. quick_sort(li,left,mid-1) # 递归
  18. quick_sort(li,mid+1,right)

快速查询的效率:
  时间复杂度: O(nlogn)
快速排序的问题:
  - 最坏情况
  - 递归(耗资源)

堆排序:

树与二叉树:

  1. 树是一种数据结构, 比如:目录结构
  2. 树是一种可以递归定义的数据结构
  3. 树是由n个节点组成的集合:
  4.   - 如果n=0,那这是一棵空树;
  5.   - 如果 n>0,那存在1个节点作为树的根节点,其他节点可以分为m个集合,每个集合本身又是一棵树

一些概念:

  1. 根节点 叶子节点(末端、没有分叉的节点即叶子节点)
  2. 树的深度(高度):最深有几层
  3. 节点的度:该节点下面分了几个叉,就代表它的度
  4. 树的度:整个树中分的最多的叉(度最多的节点)
  5. 孩子节点/父节点
  6. 子树:类似 一个大树上掰下来一个树枝,就是一个子树

二叉树:

  1. 二叉树:度不超过2的树
  2. 每个节点最多有两个孩子节点
  3. 两个孩子节点被区分为左孩子节点和右孩子节点
  4.  
  5. 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树
  6.  
  7. 完全二叉树:叶节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树

二叉树的存储方式(表示方式):
  - 链式存储方式
  - 顺序存储方式

二叉树的顺序存储方式:

  1. 父节点和左孩子节点的编号下标有什么关系
  2. - i -> 2i+1
  3. 父节点的右孩子节点的编号下标有什么关系?
  4. - i -> 2i+2

堆:

  1. 堆:一种特殊的完全二叉树结构
  2. - 大根堆: 一棵完全二叉树,满足任一节点都比其孩子节点大
  3. - 小根堆: 一棵完全二叉树,满足任一节点都比其孩子节点小

堆的向下调整:

假设:节点的左右子树都是堆,但自身不是堆; 当根节点的左右子树都是堆时,可以通过一次向下的调整来将其变换成一个堆

堆排序:

思路:

  1. 堆排序过程:
  2. 1. 建立堆
  3. 2. 得到堆顶元素,为最大元素
  4. 3. 去掉堆顶将堆最后一个元素放到堆顶,此时可通过一次调整重新使堆有序
  5. 4. 堆顶元素为第二大元素
  6. 5. 重复步骤3,直到堆变空
    堆排序的时间复杂度:O(nlogn)

示例代码:

  1. # 向下调整函数的实现
  2. # 假设节点的左右子树都是堆,但自身不是堆;当根节点的左右子树都是堆时,可以通过一次向下的调整来将其变成一个堆
  3.  
  4. def sift(li, low, high):
  5. """
  6. 节点的左右子树都是堆,但自身不是堆;通过该 sift() 的调整变成堆
  7. :param li: 列表(待处理的“堆”)
  8. :param low: 堆的根节点位置(列表的索引)
  9. :param high: 堆最后一个元素的位置
  10. :return:
  11. """
  12. i = low # 堆的父节点
  13. j = 2 * i + 1 # i 的左子节点
  14. temp = li[low] # 把堆顶存起来
  15. while j <= high: # 子节点没有超出列表的最大索引
  16. if j + 1 <= high and li[j + 1] > li[j]: # 如果有右子节点,比较左右两个子节点哪个大
  17. j = j + 1 # j 指向右子节点
  18. if li[j] > temp:
  19. li[i] = li[j] # 如果子节点比父节点大,就把子节点移到父节点的位置(不用把父节点移到子节点的位置,因为该temp还需要和下面的子节点继续比较大小)
  20. i = j # 把上次的子节点当作下次比较的父节点
  21. j = 2 * i + 1
  22. else:
  23. li[i] = temp # 如果子节点比父节点(temp)小,再把temp放到父节点的位置,同时退出循环
  24. break
  25. else: # 上面的循环如果能正常走完,说明上面的 while 循环没有被 break (即子节点一直都比父节点大),走 else 时已经是走到了最后一层
  26. li[i] = temp
  27.  
  28. def heap_sort(li):
  29. n = len(li)
  30. for i in range((n - 2) // 2, -1, -1):
  31. # i 指的是 建堆的时候调整的部分堆的根的下标
  32. """
  33. 从 (n-1)//2 的位置 倒序(每次减1)到 -1的位置;
  34. j = 2*i + 1 ==> i = (j-1)/2 ; 又因为 n为 列表的长度,所以最后一个元素的 索引为 n-1;取整是因为最后一个既有可能是左子节点也有可能是右子节点
  35. """
  36. sift(li, i, n - 1) # 用列表的长度减1作为 high 参数; high 的作用就是不让子节点越界
  37. # 通过上面的 sift() 堆已经建成
  38.  
  39. # 开始调整堆
  40. for i in range(n - 1, -1, -1):
  41. # i 表示堆的最后一个位置
  42. li[0], li[i] = li[i], li[0] # 调整位置:最后一个元素和堆顶互换
  43. sift(li, 0, i - 1) # 再重新建堆 ; i-1 是新的 high(堆顶元素已经放到了列表最后一个位置,不在计算范围之内)
  44.  
  45. li = [i for i in range(30)]
  46. import random
  47. random.shuffle(li)
  48. print(li)
  49.  
  50. heap_sort(li)
  51. print(li)

堆排序的 topk 问题:

  1. # topk 问题: 现在有n个数,设计算法得到前k大的数(k<n)
  2.  
  3. """
  4. 利用堆排序解决 topk 问题的思路:
  5. 1. 取列表前k个元素建立一个小根堆;堆顶就是目前第k大的数
  6. 2. 依次向后遍历原列表,对于列表中的元素,如果小于堆顶,慢忽略该元素;如果大于堆顶,则将堆顶换成该元素,并且对堆进行一次调整
  7. 3. 遍历列表所有元素后,倒序弹出堆顶
  8. 利用堆排序解决 topk 问题的时间复杂度: O(nlogk)
  9. """
  10.  
  11. def sift(li, low, high):
  12. """
  13. 调整为小根堆:第一个元素最小
  14. :param li: 列表(待处理的“堆”)
  15. :param low: 堆的根节点位置(列表的索引)
  16. :param high: 堆最后一个元素的位置
  17. :return:
  18. """
  19. i = low
  20. j = 2 * i + 1
  21. temp = li[low] # 把堆顶存起来
  22. while j <= high: # 子节点没有超出列表的最大索引
  23. if j + 1 <= high and li[j + 1] < li[j]: # 如果有右子节点,比较左右两个子节点哪个小
  24. j = j + 1 # j 指向右子节点
  25. if li[j] < temp:
  26. li[i] = li[j] # 如果子节点比父节点大,就把子节点移到父节点的位置(不用把父节点移到子节点的位置,因为该temp还需要和下面的子节点继续比较大小)
  27. i = j # 把上次的子节点当作下次比较的父节点
  28. j = 2 * i + 1
  29. else:
  30. li[i] = temp # 如果子节点比父节点(temp)小,再把temp放到父节点的位置,同时退出循环
  31. break
  32. else: # 上面的循环如果能正常走完,说明上面的 while 循环没有被 break (即子节点一直都比父节点大),走 else 时已经是走到了最后一层
  33. li[i] = temp
  34.  
  35. def topk(li,k):
  36. heap = li[0:k]
  37. n = len(li)
  38. for i in range((k-2)//2,-1,-1):
  39. # i 表示父节点
  40. sift(heap,i,k-1)
  41. for i in range(k,n):
  42. if li[i] > heap[0]: # li列表后面的元素和堆顶做比较
  43. heap[0] = li[i]
  44. sift(heap,0,k-1)
  45.  
  46. # 对 heap 中的元素进行排序
  47. for i in range(k-1,-1,-1):
  48. # i 表示最后一个元素的位置
  49. heap[0],heap[i] = heap[i],heap[0]
  50. sift(heap,0,i-1)
  51. return heap
  52.  
  53. li = [i for i in range(30)]
  54.  
  55. import random
  56. random.shuffle(li)
  57. print(li)
  58. print(topk(li,10))

归并排序:(merge)

假设现在的列表分两段有序,将其合成为一个有序列表,这种操作称为一次归并

归并排序的思路:

  1. 1. 分解:将列表越分越小,直至分成一个元素
  2. 2. 终止条件: 一个元素是有序的
  3. 3. 合并:将两个有序列表归并,列表越来越大

示例代码:

  1. def merge(li,low,mid,high):
  2. """一次归并"""
  3. i = low
  4. j = mid + 1
  5. temp = []
  6. while i <= mid and j <= high:
  7. if li[i] <= li[j]:
  8. temp.append(li[i])
  9. i += 1
  10. else:
  11. temp.append(li[j])
  12. j += 1
  13. # 总有一边的数会先走完
  14. while i <= mid:
  15. temp.append(li[i])
  16. i += 1
  17. while j <= high:
  18. temp.append(li[j])
  19. j += 1
  20.  
  21. li[low:high+1] = temp # 把 temp 赋值给 li的 low~high 部分(low不是从0开始)
  22.  
  23. def merge_sort(li,low,high):
  24. if low < high: # 此时至少有两个元素
  25. mid = (low+high)//2 # 取中间位置(这个也是循环条件)
  26. merge_sort(li,low,mid) # 把左边的分解、排序
  27. merge_sort(li,mid+1,high) # 把右边的分解、排序
  28. merge(li,low,mid,high) # 递归的时候把上面分解的列表元素一次次进行归并
  29.  
  30. li = list(range(100))
  31. import random
  32. random.shuffle(li)
  33. print(li)
  34.  
  35. merge_sort(li,0,len(li)-1)
  36. print("li",li)

归并排序的时间复杂度:O(nlogn)
空间复杂度:O(n) # 前面几种排序方式都是“原地排序”(没有建新的列表),但归并排序不是“原地排序”;另外 python 的sort方法就是基于“归并排序”实现的(因为归并排序是稳定式的排序)

快速排序、归并排序和堆排序小结:

  1. 1. 三种排序算法的时间复杂度都是 O(nlogn)
  2. 2. 一般情况下,就运行时间而言: 快速排序 < 归并排序 < 堆排序
  3. 3. 三种排序算法的缺点:
  4. 快速排序:极端情况下排序效率低
  5. 归并排序:需要额外的内存开销
  6. 堆排序: 在快的排序中相对较慢

算法(1):查找&排序的更多相关文章

  1. Java常用排序算法+程序员必须掌握的8大排序算法+二分法查找法

    Java 常用排序算法/程序员必须掌握的 8大排序算法 本文由网络资料整理转载而来,如有问题,欢迎指正! 分类: 1)插入排序(直接插入排序.希尔排序) 2)交换排序(冒泡排序.快速排序) 3)选择排 ...

  2. PHP数组基本排序算法和查找算法

    关于PHP中的基础算法,小结一下,也算是本博客的第一篇文章1.2种排序算法冒泡排序:例子:个人见解 5 6 2 3 7 9 第一趟 5 6 2 3 7 9 5 2 6 3 7 9 5 2 3 6 7 ...

  3. 常见的排序算法(直接插入&选择排序&二分查找排序)

    1.直接插入排序算法 源码: package com.DiYiZhang;/* 插入排序算法 * 如下进行的是插入,排序算法*/ public class InsertionSort {    pub ...

  4. 20162311 编写Android程序测试查找排序算法

    20162311 编写Android程序测试查找排序算法 一.设置图形界面 因为是测试查找和排序算法,所以先要有一个目标数组.为了得到一个目标数组,我设置一个EditText和一个Button来添加数 ...

  5. 排序算法总结------选择排序 ---javascript描述

    每当面试时避不可少谈论的话题是排序算法,上次面试时被问到写排序算法,然后脑袋一懵不会写,狠狠的被面试官鄙视了一番,问我是不是第一次参加面试,怎么可以连排序算法都不会呢?不过当时确实是第一次去面试,以此 ...

  6. Java中常用的查找算法——顺序查找和二分查找

    Java中常用的查找算法——顺序查找和二分查找 神话丿小王子的博客 一.顺序查找: a) 原理:顺序查找就是按顺序从头到尾依次往下查找,找到数据,则提前结束查找,找不到便一直查找下去,直到数据最后一位 ...

  7. Java常见排序算法之Shell排序

    在学习算法的过程中,我们难免会接触很多和排序相关的算法.总而言之,对于任何编程人员来说,基本的排序算法是必须要掌握的. 从今天开始,我们将要进行基本的排序算法的讲解.Are you ready?Let ...

  8. 插入排序算法--直接插入算法,折半排序算法,希尔排序算法(C#实现)

    插入排序算法主要分为:直接插入算法,折半排序算法(二分插入算法),希尔排序算法,后两种是直接插入算法的改良.因此直接插入算法是基础,这里先进行直接插入算法的分析与编码. 直接插入算法的排序思想:假设有 ...

  9. JS中的算法与数据结构——排序(Sort)(转)

    排序算法(Sort) 引言 我们平时对计算机中存储的数据执行的两种最常见的操作就是排序和查找,对于计算机的排序和查找的研究,自计算机诞生以来就没有停止过.如今又是大数据,云计算的时代,对数据的排序和查 ...

  10. JS中的算法与数据结构——排序(Sort)

    排序算法(Sort) 引言 我们平时对计算机中存储的数据执行的两种最常见的操作就是排序和查找,对于计算机的排序和查找的研究,自计算机诞生以来就没有停止过.如今又是大数据,云计算的时代,对数据的排序和查 ...

随机推荐

  1. 不通过getElementByName实现获取表单数据 (document.form表单的name值.input输入框的name值)

    function update() { //document.form表单的name值.input输入框的name值 var username = document.form1.username; v ...

  2. 227 Basic Calculator II 基本计算器II

    实现一个基本的计算器来计算一个简单的字符串表达式. 字符串表达式仅包含非负整数,+, - ,*,/四种运算符和空格 . 整数除法仅保留整数部分. 你可以假定所给定的表达式总是有效的. 一些例子: &q ...

  3. Kali linux 2016.2(Rolling)里安装中文输入法

    写在前面的话 关于中文输入法,实在是有太多了.当然,你也不可以不安装,(安装了增强工具即可),在windows 里输入中文,复制进去即可. 但是呢,想成为高手,还是要学会安装和使用各版本的中文输入法. ...

  4. 一个页面通过iframe,获取另一个页面的form

    document.getElementsByTagName("iframe")[0].contentWindow.document.forms[0].submit(); var z ...

  5. (六)Mybatis总结之延迟加载

    应用场景: i.假如一个用户他有N个订单(N>=1000),那么如果一次性加载的话,一个用户对象的订单集合OrderList里面就会有1000多个Order的对象.计算:一个订单对象里面数据有多 ...

  6. Objective-C Memory Management 内存管理 2

    Objective-C Memory Management 内存管理  2  2.1 The Rules of Cocoa Memory Management 内存管理规则 (1)When you c ...

  7. Nodejs AES加密不一致问题的解决

    最近在做android游戏,客户端与Nodejs服务端数据的交互用AES进行加密,发现Nodejs与java的加密形式不一样.查询N久资料发现java端需要对密钥再MD5加密一遍(我了个大擦),本来对 ...

  8. JPA调用存储过程

    @Transactional public BasAccount findByAccount(String account) { System.out.println(account); Query ...

  9. (三)Redis for StackExchange.Redis

    目录 (一)Redis for Windows正确打开方式 (二)Redis for 阿里云公网连接 (三)Redis for StackExchange.Redis StackExchange.Re ...

  10. win8怎么打开或关闭快速启动(进入BIOS前的设置)

    win8系统之后,系统添加了快速启动功能,这让Windows的启动速度快了不少.但是,任何事物有利有弊,相信不少人在进入BIOS或者重装系统时遇到了麻烦.接下来我们看看在win8及以上版本怎么打开或关 ...