数据结构及算法思想的实现

数据结构及算法思想的实现

0. 算法评价

0.1. 时间消耗

vim  python_file.py
---
print ("hello world")
---
time python python_file.py
>>> 
    real    0m0.146s
    user    0m0.047s
    sys     0m0.063s

0.2. 空间消耗

import sys
sys.getsizeof()
>>> 123 #betye

VSS- Virtual Set Size 虚拟耗用内存(包含共享库占用的内存)
RSS- Resident Set Size 实际使用物理内存(包含共享库占用的内存)
PSS- Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)
USS- Unique Set Size 进程独自占用的物理内存(不包含共享库占用的内存)

一般来说内存占用大小有如下规律:VSS >= RSS >= PSS >= USS

0.3. 时间复杂度/空间复杂度

大O法(Order):$O(n)$

  1. 空间复杂度(Space Complexity)
    执行这个算法所需要的内存空间 $S(n)=···=O(n)$

  2. 时间复杂度(Time Complexity)
    时间复杂度是指执行算法所需要的计算工作量 $T(n)=···=O(n)$

0.3.1 工具

1. 基础数据结构

1.1 链表

非连续内存存储
类型:

1.1.1 基本链表

# python
class ListNode(object):
    def __init__(self,value=None):
        self.data=value
        self.next=None
    # 获得数据
    def getData(self):
        return self.data
    # 获得下一个节点的引用
    def getNext(self):
        return self.next
    # 修改数据
    def setData(self, newdata):
        self.data = newdata
    # 修改下一节点的引用
    def setNext(self, newnext):
        self.next = newnext
struct ListNode
{
    double value;
    ListNode *next;
};

1.1.2. 双向链表

1.1.3. 循环链表

1.2 数组

连续内存存储

  1. python
import numpy as np

np.array(···,dtype=None,shape=[])

| 类型 | 说明 | | :---------- | :--------------------------- | | numpy array | 内存中一个连续块 | | list | 每个元素其实是一个地址的引用 | 一个numpy array需要确定变量类型(int/float32)和大小,然后在 是内存中申请一个确定大小的连续块。 list完全不同,它的每个元素其实是一个地址的引用,这个地址又指向了另一个元素,这些元素的在内存里不一定是连续的。所以list其实是只能塞进地址的“数组”,而且由于地址不用连续,每当我想加入新元素,我只用把这个元素的地址添加进list

list_1=["abc","defffff","ghiiiiiiiiiii"]
list_2=["abc","defffff"]
list_3=["abc"]
array_1=np.array(list_1)
array_2=np.array(list_2)
array_3=np.array(list_3)

print (sys.getsizeof(list_1)-sys.getsizeof(list_2)) #8 
print (sys.getsizeof(list_2)-sys.getsizeof(list_3)) #8
print (sys.getsizeof(list_3)) #72

print (sys.getsizeof(array_1)-sys.getsizeof(array_2)) #100
print (sys.getsizeof(array_2)-sys.getsizeof(array_3)) # 44
print (sys.getsizeof(array_3)) # 108

print(sys.getsizeof("abc")) #60
print (sys.getsizeof("defffff")) #72
print (sys.getsizeof("ghiiiiiiiiiii")) #90 
  1. c++
// 1维数组

int array[100];   //定义了数组array,并未对数组进行初始化
int array[100] = {12}  //定义并初始化了数组array

int* array = new int[100];  
delete []array;  //分配了长度为100的数组array 

int* array = new int[100](12);  
delete []array; //为长度为100的数组array初始化前两个元素

//2 维数组

int array[10][10];  //定义了数组,并未初始化
int array[10][10] = { {1,1} , {2,2} };  //数组初始化了array[0][0,1]及array[1][0,1]

int (*array)[n] = new int[m][n];
delete []array;

int** array = new int*[m]; 
for(i) array[i] = new int[n];  
for(i) delete []array[i]; 
delete []array;    //多次析构
int* array = new int[m][n];  
delete []array;      //数组按行存储

//多维数组
int* array = new int[m][3][4];    //只有第一维可以是变量,其他维数必须是常量,否则会报错
delete []array;       //必须进行内存释放,否则内存将泄漏

1.3 栈

LIFO队列,Last in First Out,后进先出

class Stack(object):
    def __init__(self):
        self.stack=[]
    def isEmpty(self):
        return self.stack==[]
    def push(self,item):
        self.stack.append(item)
    def pop(self):
        if self.isEmpty():
            raise IndexError,'pop from empty stack'
        return self.stack.pop()
    def peek(self):
        return self.stack[-1]
    def size(self):
        return len(self.stack)

# 内置
from queue import LifoQueue #LIFO队列,Last in First Out,后进先出
lifoQueue = LifoQueue()
lifoQueue.put(1)
lifoQueue.put(2)
lifoQueue.put(3)
print('LIFO队列',lifoQueue.queue)
lifoQueue.get() #返回并删除队列尾部元素
lifoQueue.get()
print(lifoQueue.queue)

1.4 队列

Last in Last Out,后进后出

class Queue(object):
    def __init__(self):
        self.queue=[]
    def isEmpty(self):
        return self.queue==[]
    def enqueue(self,x):
        self.queue.append(x)
    def dequeue(self):
        if self.queue:
            a=self.queue[0]
            self.queue.remove(a)
            return a
        else:
            raise IndexError,'queue is empty'
    def size(self):
        return len(self.queue)

# 内置
from queue import Queue #LILO队列 Last in Last Out,后进后出
q = Queue() #创建队列对象
q.put(0)    #在队列尾部插入元素
q.put(1)
q.put(2)
print('LILO队列',q.queue)  #查看队列中的所有元素
print(q.get())  #返回并删除队列头部元素
print(q.queue)

from queue import PriorityQueue #优先队列
priorityQueue = PriorityQueue() #创建优先队列对象
priorityQueue.put(3)    #插入元素
priorityQueue.put(78)   #插入元素
priorityQueue.put(100)  #插入元素
print(priorityQueue.queue)  #查看优先级队列中的所有元素
priorityQueue.put(1)    #插入元素
priorityQueue.put(2)    #插入元素
print('优先级队列:',priorityQueue.queue)  #查看优先级队列中的所有元素
priorityQueue.get() #返回并删除优先级最低的元素
print('删除后剩余元素',priorityQueue.queue)
priorityQueue.get() #返回并删除优先级最低的元素
print('删除后剩余元素',priorityQueue.queue)  #删除后剩余元素
priorityQueue.get() #返回并删除优先级最低的元素
print('删除后剩余元素',priorityQueue.queue)  #删除后剩余元素
priorityQueue.get() #返回并删除优先级最低的元素
print('删除后剩余元素',priorityQueue.queue)  #删除后剩余元素
priorityQueue.get() #返回并删除优先级最低的元素
print('全部被删除后:',priorityQueue.queue)  #查看优先级队列中的所有元素

from collections import deque   #双端队列
dequeQueue = deque(['Eric','John','Smith'])
print(dequeQueue)
dequeQueue.append('Tom')    #在右侧插入新元素
dequeQueue.appendleft('Terry')  #在左侧插入新元素
print(dequeQueue)
dequeQueue.rotate(2)    #循环右移2次
print('循环右移2次后的队列',dequeQueue)
dequeQueue.popleft()    #返回并删除队列最左端元素
print('删除最左端元素后的队列:',dequeQueue)
dequeQueue.pop()    #返回并删除队列最右端元素
print('删除最右端元素后的队列:',dequeQueue)

1.5 哈希表

哈希表,一种key-value形式的数据结构

场景
1. 数据量大了之后,线性插值某一value 资源消耗大

构建哈希表的关键在于运用1.哈希函数及相应的数学运算2. 冲突管理机制构建哈希表地址
关键点:
1. 哈希函数
2. 冲突处理`

    address_in_table=hash(key)mod(n)
    if address_in_table 相同:
        依据冲突处理规则处理
    return address_in_table

1. 哈希(散列/摘要)函数

功能:将给定数据转换成为固定长度的无规律数值
特性:
1. 函数输出的长度相同
2. 相同输入数据的输出相同
3. 不同数据输出大概率不同
4. 不同输入的输出也有可能相同(概率低),若发生则成为哈希冲突
5. 不可逆,不可以通过输出反推出输入值
6. 正向计算较为容易

类型 描述
md4 MD4(RFC 1320)是 MIT 的Ronald L. Rivest在 1990 年设计的,MD 是 Message Digest 的缩写。它适用在32位字长的处理器上用高速软件实现--它是基于 32位操作数的位操作来实现的。
md5 MD5(RFC 1321)是 Rivest 于1991年对MD4的改进版本。它对输入仍以512位分组,其输出是4个32位字的级联,与 MD4 相同。MD5比MD4来得复杂,并且速度较之要慢一点,但更安全,在抗分析和抗差分方面表现更好
sha1 SHA1是由NIST NSA设计为同DSA一起使用的,它对长度小于2^64位的输入,产生长度为160bit的散列值,因此抗穷举(brute-force)性更好。SHA-1 设计时基于和MD4相同原理,并且模仿了该算法。
sha2 较常使用
import hashlib

md5 = hashlib.md5()
md5.update('how to use md5 in python hashlib?'.encode('utf-8'))
print(md5.hexdigest())

>>> d26a53750bc40b38b65a520292f69306

哈希表地址的表示方法
(1) 除法散列法

哈希表的大小为m行,通过取哈希值hash_num除以m的余数,将关键字映射到m个槽中去,即

hash_num=hash(key)
$$address_in_table= mod(hash_num,m)$$

其中m的取值很重要,这个函数得出的散列地址值不会超过m,同时可以选作p的值常常是与2的整数幂不太接近的质数。当存放位置m较大时,p不宜过小。

(2)乘法散列法

$$h(hash_ num)=[p(hash_numA-(int)hash_num*A)]$$

用hash_num乘以常数A,并且抽出kA的小数部分,然后用m乘以这个值,再取底floor.

其中0<A<1,一般取,$(\sqrt5-1)/2$,m没有太多限制,一般取2的整数次幂

(3)全域散列

2. 冲突处理

当数据量大了之后,哈希地址表就不是唯一的,就存在冲突。

2.1 开放寻址法
2.2 链地址法

把散列到同一个槽中的所有元素都放在一个链表中。相对于开放地址法,可能会增加存储空间。

2.3 建立一个公共溢出区

若发生冲突,把key存入公共溢出区。

3. 实现

在Python中,dict的底层是依靠哈希表(Hash Table)进行实现的,使用开放地址法解决冲突。所以其查找的时间复杂度会是O(1)

# python dict 
dict={key:value}
#include <iostream> 
#include <vector> 
#include <unordered_map>
using namespace std; 

class Myclass { 
    public: int first; 
    vector<int> second; // 重载等号,判断两个Myclass类型的变量是否相等 
    bool operator== (const Myclass &other) const { 
        return first == other.first && second == other.second; } }; // 实现Myclass类的hash函数 

    namespace std { 
        template <> struct hash<Myclass> { 
            size_t operator()(const Myclass &k) const 
            {   int h = k.first;
                for (auto x : k.second) { 
                    h ^= x;
                    } 
                return h;
                } 
        }; 
    } 

int main()
{ 
    unordered_map<Myclass, double> S; 
    Myclass a = { 2, {3, 4} }; 
    Myclass b = { 3, {1, 2, 3, 4} };
    S[a] = 2.5; 
    S[b] = 3.123; 
    cout << S[a] << ' ' << S[b] << endl; 
    return 0;
 }
class LinearMap(object):
    def __init__(self):
         self.items = []
    # 往表中添加元素    
    def add(self, k, v):  
        self.items.append((k,v))

    # 线性方式查找元素
    def get(self, k): 
        for key, value in self.items:      
            if key == k:      # 键存在,返回值,否则抛出异常
                return value
        raise KeyError

1.7 树

1.7.1 二叉树

class BinaryTree:
    def __init__(self,rootObj):
        self.key = rootObj
        self.leftChild = None
        self.rightChild = None

    def insertLeft(self,newNode):
        if self.leftChild == None:
            self.leftChild = BinaryTree(newNode)
        else:
            t = BinaryTree(newNode)
            t.leftChild = self.leftChild
            self.leftChild = t

    def insertRight(self,newNode):
        if self.rightChild == None:
            self.rightChild = BinaryTree(newNode)
        else:
            t = BinaryTree(newNode)
            t.rightChild = self.rightChild
            self.rightChild = t
    def getRightChild(self):
        return self.rightChild

    def getLeftChild(self):
        return self.leftChild

    def setRootVal(self,obj):
        self.key = obj

    def getRootVal(self):
        return self.key
1.7.1.1 二叉查找树
class BinarySearchTree(object):
    def __init__(self,key):
        self.key=key
        self.leftChild=None
        self.rightChild=None

    def search(self, node, parent, data):
        if node is None:
            return False, node, parent
        if node.data == data:
            return True, node, parent
        if node.data > data:
            return self.search(node.leftChild, node, data)
        else:
            return self.search(node.rightChild, node, data)
        # 插入
    def insert(self, data):
        flag, n, p = self.search(self.root, self.root, data)
        if not flag:
            new_node = BinarySearchTree(data)
        if data > p.data:
            p.leftChild = new_node
        else:
            p.rightChild = new_node
1.7.1.2 堆

堆(heap)又被为优先队列(priority queue)

import heapq 
heap = []            # creates an empty heap
heappush(heap, item) # pushes a new item on the heap
item = heappop(heap) # pops the smallest item from the heap
item = heap[0]       # smallest item on the heap without popping it
heapify(list_x)           # transforms list into a heap, in-place, in linear time
item = heapreplace(heap, item) # 返回最小值,同时将新item加入堆中,堆的大小不变

2.基础算法思想

1. 枚举/遍历

1.1 枚举的基本思想

枚举是基于已有知识进行答案猜测的一种问题求解策略。

枚举从可能的集合中一一列举各元素, 对问题可能解集合的每一项,根据问题给定的检验条件判定哪些是成立的,使条件成立的即是问题的解。

1.2 枚举过程

1.3 枚举中的三个关键问题

  1. 给出解空间

    • 给出解空间,建立简洁的数学模型
    • 模型中变量数尽可能少, 它们之间相互独立
  2. 减少搜索空间

    • 利用知识缩小模型中各变量的取值范围, 避免不必要的计算
    • 减少代码中循环体执行次数
  3. 合适的搜索顺序
    • 搜索空间的遍历顺序要与模型中条件表达式一致
D={}
while (没有找到正确答案)
    正确答案=Di?
    i+=1

双指针枚举

双指针主要用于遍历数组,两个指针指向不同的元素,从而协同完成任务。
双指针:
1. 慢指针
2. 快指针
双指针可以从不同的方向向中间逼近也可以朝着同一个方向遍历

2. 递归

分治法就是把1个分为多个,递归法就是把多个归一的解决问题方法。
递归问题是一种层次结构的分治策略--拆解成小问题--判断停止条件

2.1 递归的基本思想

递归 — 某个函数直接或间接的调用自身
问题的求解过程

这些子问题的解就构成里原问题的解

2.2 递归与枚举

枚举把一个问题划分成一组子问题,依次对这些子问题求解

把一个问题逐级分解成子问题

2.3 三个要点

2.4 关键--递推公式\递归中止条件

注意事项: 由于函数的局部变量是存在栈上的,如果有体积大的局部变量(比如数组)而递归层次可能很深的情况下,也许会导致栈溢出。可以考虑使用全局数组或动态分配数组。

function (n){
    return function (n-1) ;
}

3. 动态规划

3.1 动态规划的基本思想

int* product =new product[3]
product[1]=~~

function (n){

    return function (n-1) ;
}

3.2 动规解题的一般思路:

  1. 将原问题分解为子问题
  2. 把原问题分解为若干个子问题,子问题和原问题形式相同 或类似,只不过规模变小了。子问题都解决,原问题即解决。
  3. 子问题的解一旦求出就会被保存,所以每个子问题只需求解一次

  4. 确定状态

状态:在用动态规划解题时,我们往往将和子问题相关的各个变量的一组取值,称之为一个状态
状态的值:就是这个状态所对应的子问题的解

状态空间所有状态的集合,构成问题的状态空间

状态空间的大小,与用动态规划解决问题的时间复杂度直接相关。

在数字三角形的例子里,一共有N×(N+1)/2个数字,所以这个 问题的状态空间里一共就有N×(N+1)/2个状态。

整个问题的时间复杂度是状态数目乘以计算每个状态所需时间。

在数字三角形里每个“状态”只需要经过一次,且在每个 状态上作计算所花的时间都是和N无关的常数。

  1. 确定一些初始状态(边界状态)的值

    以“数字三角形”为例,初始状态就是底边数字,值 就是底边数字值。

  2. 确定状态转移方程

    定义出什么是“状态”,以及在该 “状态”下的“值”后,就要 找出不同的状态之间如何迁移――即如何从一个或多个“值”已知的 “状态”,求出另一个“状态”的“值”(“人人为我”递推型)。状 态的迁移可以用递推公式表示,此递推公式也可被称作“状态转移方 程”。 数字三角形的状态转移方程:

可用用动态规矩解决的问题特点
1) 问题具有最优子结构性质。如果问题的最优解所包含的 子问题的解也是最优的,我们就称该问题具有最优子结 构性质。
2) 无后效性。当前的若干个状态值一旦确定,则此后过程的演变就只和这若干个状态的值有关,和之前是采取哪 种手段或经过哪条路径演变到当前的这若干个状态,没 有关系。

avatar

4. 深度优先搜索

4.1 dfs

说明:
此时所在的顶点 node
候选节点 candidate_node_stack
选择的节点 node_i

Dfs(node) {
    //可行性减支
    if(node 访问过)
        return ;  
    node标记为访问过;
    node相邻的点加入到candidate_node_stack 
    candidate_node_stack里面确定下一个点node_i:  
    Dfs(node_i);
    }

int main() {
    while(在图中能找到未访问过的点k) {
         Dfs(k);
    } 
         }
class Graph(object):
  def __init__(self,*args,**kwargs):
    self.node_neighbors = {}
    self.visited = {}
  def add_nodes(self,nodelist):
    for node in nodelist:
      self.add_node(node)
  def add_node(self,node):
    if not node in self.nodes():
      self.node_neighbors[node] = []
  def add_edge(self,edge):
    u,v = edge
    if(v not in self.node_neighbors[u]) and ( u not in self.node_neighbors[v]):
      self.node_neighbors[u].append(v)
      if(u!=v):
        self.node_neighbors[v].append(u)
  def nodes(self):
    return self.node_neighbors.keys()
  def depth_first_search(self,root=None):
    order = []
    def dfs(node):
      self.visited[node] = True
      order.append(node)
      for n in self.node_neighbors[node]:
        if not n in self.visited:
          dfs(n)
    if root:
      dfs(root)
    for node in self.nodes():
      if not node in self.visited:
        dfs(node)
    print order
    return order
  def breadth_first_search(self,root=None):
    queue = []
    order = []
    def bfs():
      while len(queue)> 0:
        node = queue.pop(0)
        self.visited[node] = True
        for n in self.node_neighbors[node]:
          if (not n in self.visited) and (not n in queue):
            queue.append(n)
            order.append(n)
    if root:
      queue.append(root)
      order.append(root)
      bfs()
    for node in self.nodes():
      if not node in self.visited:
        queue.append(node)
        order.append(node)
        bfs()
    print order
    return order

4.2 剪枝

剪枝:把不会产生答案的,或不必要的枝条“剪掉”。

即在设计剪枝判断方法的基础上,避免一些不必要的遍历过程,从而提高算法效率。

4.2.1 剪枝的关键

剪枝的关键就在于剪枝的判断:什么枝该剪,什么枝不该剪,在什么地方减

4.2.2 剪枝的原则

正确性,准确性,高效性。

4.2.3 常用的剪枝
4.2.3.1. 可行性剪枝

如果当前条件不合法就不再继续搜索,直接return。这是非常好理解的剪枝。一般的搜索都会加上。

一般格式:

dfs(int x)
{
    if(x>n)return;
    if(!check1(x))return;
    ....
    return;
}
4.2.3.2. 最优性剪枝

如果当前条件所创造出的答案必定比之前的答案大,那么剩下的搜索就毫无必要,甚至可以剪掉。
我们利用某个函数估计出此时条件下答案的‘下界’,将它与已经推出的答案相比,如果不比当前答案小,就可以剪掉。

一般格式:

long long ans=987474477434487ll;
dfs(int x,...)
{
    if(x... && ...)
    {
        ans=....;
        return ...;
    }
    //最优性剪枝 
    if(check2(x)>=ans)
    {
        return ...;
    }
    for(int i=1;...;++i)
        {
            vis[...]=1; 
            dfs(...);
            vis[...]=0;
        }
}

一般实现:
在搜索取和最大值时,如果后面的全部取最大仍然不比当前答案大就可以返回。
在搜和最小时同理,可以预处理后缀最大/最小和进行快速查询。

4.2.3.3. 记忆化搜索

记忆化搜索其实很像动态规划(DP)。

它的关键是:如果对于相同情况下必定答案相同,就可以把这个情况的答案值存储下来,以后再次搜索到这种情况时就可以直接调用。

还有就是不能搜出环来,不能互相依赖。

一般格式:

long long ans=987474477434487ll;
dfs(int x,...)
{
    if(x... && ...)
    {
        ans=....;
        return ...;
    }
    if(vis[x]!=0)
        return f[x];
        vis[x]=1;
    for(int i=1;...;++i)
        {
            vis[...]=1;
            dfs(...);
            vis[...]=0;
            f[x]=...;
        }
}
4.2.3.4. 搜索顺序剪枝

在一些迷宫题,网格题,或者其他搜索中可以贪心的题,搜索顺序显得十分重要,好的顺序可以提高算法效率。

  1. 在迷宫、网格类的题目中,右下左上就明显比左上右下优秀。
  2. 推断搜索题中,从已知信息最多的地方开始搜索显然更加优秀。
  3. 在一些题中,先搜某个值大的,再搜某个值小的(比如树的度数,产生答案的预计(A*)),速度明显会比乱搜更快。

5. 广度优先搜索

5.1 核心思想

广索的基本方法是使用队列存放已经扩展过的节点

适用问题:最短路径算法

此时所在的顶点 node
候选节点 candidate_node_queue
选择的节点 node_i

BFS(){
        初始化队列
        while(队列不为空且未找到目标节点) 
        {
            取队首节点扩展,并将扩展出的非重复节点放入队尾 
            必要时记住每个节点的父节点;
        }
}

6. 二分

二分法是在有序或单调的区间中快速寻找答案的有效方法,当数据量很大适宜采用该方法。
时间复杂度 O(logN)

7. 贪心

贪心(greedy algorithm)
过程:

1. 建立数学模型来描述问题;
2. 把求解的问题分成若干个子问题;
3. 对每一子问题求解,得到子问题的局部最优解;
4. 把子问题的解局部最优解合成原来解问题的一个解。

所谓贪心算法,即总是作出在当前看来最好的选择。也就是说贪心算法并不从整体最优考虑,它所作出的选择只是在某种意义上的局部最优选择。但是,贪心算法对很多问题都能得到整体最优解。在一些情况下,即使贪心算法不能得到整体最优解,其最终结果却是最优解的很好近似。贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择。

3.基于问题的划分

3.1 排序

复杂度
插入排序 直接插入排序 $O(n^2)$
插入排序 希尔排序 $O(n\log n)$
选择排序 简单选择排序 $O(n^2)$
选择排序 堆排序 $O(n\log n)$
交换排序 冒泡排序 $O(n^2)$
交换排序 快速排序 $O(n\log n)$
归并排序 归并排序 $O(n\log n)$
* 记忆: 涉及二分/树 问题为 $O(nlogn)$
#### 3.1.1. 插入排序
##### 直接插入排序
##  插入排序
### 打牌 拿牌的时候排序
def insert_sort(raw_list):
    # raw_list:[2,5,3,5 ]
    # sorted_list:[2,5]
    for flag_index in range(1,len(raw_list)):
        insert_value = raw_list[flag_index]
        insert_index=flag_index
        # insert_value:3
        if insert_value < raw_list[flag_index-1]:
            # if 3 < 5
            for j in range(insert_index-1,-1,-1):  # 对于  sorted_list
                if raw_list[j] > insert_value :
                    # if 5>3
                    raw_list[j+1] = raw_list[j]
                    # 
                    insert_index = j   #记录待插入下标
                else :
                    break
            raw_list[insert_index] = insert_value
        print ("flag_index {} 的 {}:{}".format(flag_index,raw_list[flag_index],raw_list))
    return raw_list

myList = [49,38,65,97,76,13,27,49]
insert_sort(myList)
print(myList)


flag_index 1  49:[38, 49, 65, 97, 76, 13, 27, 49]
flag_index 2  65:[38, 49, 65, 97, 76, 13, 27, 49]
flag_index 3  97:[38, 49, 65, 97, 76, 13, 27, 49]
flag_index 4  97:[38, 49, 65, 76, 97, 13, 27, 49]
flag_index 5  97:[13, 38, 49, 65, 76, 97, 27, 49]
flag_index 6  97:[13, 27, 38, 49, 65, 76, 97, 49]
flag_index 7  97:[13, 27, 38, 49, 49, 65, 76, 97]
希尔排序
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#插入排序,缩小增量
def shell_sort(raw_list):
    gap = len_of_list = len(raw_list)
    result_list=[]
    while(gap > 1):
        gap = gap/2
        for i in range(0, len_of_list):
            for j in range(i, len_of_list - gap, gap):
                if result_list[j] > result_list[j+gap]:
                    result_list[j], result_list[j+gap] = result_list[j+gap], result_list[j]
    return result_list

3.1.2 选择排序

简单选择排序
## 1.1 选择排序
### 先选第一个,确定第一个最小,再确定第二个 
def select_sort(raw_list):
    sorted_list=raw_list
    len_of_list=len(raw_list)
    for i in range(0, len_of_list- 1):
        selected_index = i
        for j in range(i + 1, len_of_list):
            if sorted_list[selected_index] > sorted_list[j]:
                selected_index = j
            sorted_list[i], sorted_list[selected_index] = sorted_list[selected_index], sorted_list[i]
        print ("选择 {}:{}".format(raw_list[i],sorted_list))
    return sorted_list
s = [3, 4, 1, 6, 2, 9, 7, 0, 8, 5]
select_sort(s)

选择 0:[0, 4, 3, 6, 2, 9, 7, 1, 8, 5]
选择 1:[0, 1, 3, 6, 4, 9, 7, 2, 8, 5]
选择 2:[0, 1, 2, 6, 4, 9, 7, 3, 8, 5]
选择 3:[0, 1, 2, 3, 6, 9, 7, 4, 8, 5]
选择 4:[0, 1, 2, 3, 4, 9, 7, 6, 8, 5]
选择 5:[0, 1, 2, 3, 4, 5, 9, 6, 8, 7]
选择 6:[0, 1, 2, 3, 4, 5, 6, 9, 8, 7]
选择 7:[0, 1, 2, 3, 4, 5, 6, 7, 9, 8]
选择 8:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
堆排序
def heap_adjust(L, start, end):
    temp = L[start]

    i = start
    j = 2 * i

    while j <= end:
        if (j < end) and (L[j] < L[j + 1]):
            j += 1
        if temp < L[j]:
            L[i] = L[j]
            i = j
            j = 2 * i
        else:
            break
    L[i] = temp

def heap_sort(raw_list):
    len_of_list = len(raw_list)

    first_sort_count = (len_of_list-1) / 2
    for i in range(first_sort_count):
        heap_adjust(L, first_sort_count - i, len_of_list)

    for i in range(len_of_list - 1):
        L = swap_param(L, 1, len_of_list - i)
        heap_adjust(L, 1, len_of_list - i - 1)

    return [L[i] for i in range(1, len(L))]

3.1.3. 交换排序

冒泡排序
## 1.3 交换排序
def bubble_sort(raw_list):
    """冒泡排序 相邻交换, 进行第几轮 最后面的第几个数值确定"""
    print (raw_list)
    for i in range(len(raw_list)-1):    # 这个循环负责设置冒泡排序进行的次数
        print ("交换前{}个数字 ".format(len(raw_list)-i-1))
        for j in range(len(raw_list)-i-1):  # j为列表下标
            if raw_list[j] > raw_list[j+1]:
                raw_list[j], raw_list[j+1] = raw_list[j+1], raw_list[j]
                print ("{}   {}<->{} :{}".format(j,raw_list[j], raw_list[j+1],raw_list))
            else:
                print ("{}   {} --{}: FALSE".format(j,raw_list[j] ,raw_list[j+1]))
    return raw_list

nums = [5,2,4,6,8,2,1]
bubble_sort(nums)

[5, 2, 4, 6, 8, 2, 1]
交换前6个数字 
0   2<->5 :[2, 5, 4, 6, 8, 2, 1]
1   4<->5 :[2, 4, 5, 6, 8, 2, 1]
2   5 --6: FALSE
3   6 --8: FALSE
4   2<->8 :[2, 4, 5, 6, 2, 8, 1]
5   1<->8 :[2, 4, 5, 6, 2, 1, 8]
交换前5个数字 
0   2 --4: FALSE
1   4 --5: FALSE
2   5 --6: FALSE
3   2<->6 :[2, 4, 5, 2, 6, 1, 8]
4   1<->6 :[2, 4, 5, 2, 1, 6, 8]
交换前4个数字 
0   2 --4: FALSE
1   4 --5: FALSE
2   2<->5 :[2, 4, 2, 5, 1, 6, 8]
3   1<->5 :[2, 4, 2, 1, 5, 6, 8]
交换前3个数字 
0   2 --4: FALSE
1   2<->4 :[2, 2, 4, 1, 5, 6, 8]
2   1<->4 :[2, 2, 1, 4, 5, 6, 8]
交换前2个数字 
0   2 --2: FALSE
1   1<->2 :[2, 1, 2, 4, 5, 6, 8]
交换前1个数字 
0   1<->2 :[1, 2, 2, 4, 5, 6, 8]
快速排序
# 快速排序
## 1.取基准值
## 2. 确保基准值 左<右
def quick_sort(raw_list):
    """快速排序""" 
    if len(raw_list) >= 2:  # 递归入口及出口
        pivot = raw_list[len(raw_list)//2]  # 选取基准值,也可以选取第一个或最后一个元素      
        left, right = [], []  # 定义基准值左右两侧的列表
        raw_list.remove(pivot)  # 从原始数组中移除基准值
        for num in raw_list:
            if num >= pivot:
                right.append(num)
            else:
                left.append(num)
        sorted_list=quick_sort(left) + [pivot] + quick_sort(right)    
    else:
        sorted_list=raw_list
    return sorted_list
# 示例:
array = [2,3,5,7,1,4,6,15,5,2,7,9,12]
print(quick_sort(array))

[1, 2, 2, 3, 4, 5, 5, 6, 7, 7, 9, 12, 15]

3.1.4 归并排序

3.2 查找

3.2.1. 二分查找

针对已经排好序的数据结构,从中间开始查找,比较大小
时间复杂度O(logN)

3.2.2. 顺序查找

时间负责度O(n)

3.2.3 哈希表查找

时间复杂度 O(1)

3.2.4 二叉搜索树查找

3.3 图的搜索

3.3.1 树的遍历

3.3.1.1 前序
3.3.1.2 中序
3.3.1.3 后序

3.3.2 广度优先搜索

3.3.3 深度优先搜索

参考资料

[1] 我的第一步算法书
[2] 5分钟


如果你觉得这篇文章对你有帮助,不妨请我喝杯咖啡,鼓励我创造更多!