查找&排序

查找:

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

列表查找(线性表查找)

从列表中查找指定的元素

  输入:列表、待查找元素

  输出:元素下标(未找到元素的时候一般返回None或者-1)

顺序查找(Linear_Search):

def Linear_search(li, val):
    for index, v in enumerate(li):
        if v == val:
            return index
    else:
        return

n为列表长度,没有循环迭代的过程

时间复杂度:

O(n)

二分查找(Binary_Search):

又叫折半查找,从有序列表的初始候选区li[0:n]开始,通过对待查找的值与候选区中间值的比较,可以使候选区减少一半

right = mid - 1

 新的mid 就是2

 left = mid + 1

def Binary_Search(li, val):
    left = 0
    right = len(li)-1
    while left <= right:     # 候选区还有数值
        mid = (left + right) // 2
        if li[mid] == val:
            return mid
        elif li[mid] > val:  # 查找的值在mid左侧
            right = mid - 1  # !!!!!!!!!!!!!!!!!!!!!!!!
        else:  # li[mid] < val 说明带查找的值在mid的右侧
            left = mid + 1
    else:
        return None

  时间复杂度为  O(logn)

排序:

Bubble Sort (冒泡排序)

列表每两个相邻的数,如果前面比后面大,那么交换这两个数。

一趟排序完成后,则无序区减少一个数,有序区增加一个数。(出现最大的数)(n-1趟)

代码关键: 趟、无序区

时间复杂度 : O(n^2)

def bubble_sort(li):
    for i in range(len(li) - 1):  # 第i趟
        exchange = False
        for j in range(len(li) - i - 1):  # 为什么要减去i,因为冒泡排序随每i趟而减少循环i次, 这里是无序区的数  @@@@!!!!!!!!!!!!!!!!!!!!!!!
            if li[j] > li[j+1]:  # reversed 就为 降序
                li[j], li[j+1] = li[j+1], li[j]  # 交换
                exchange = True
        if not exchange:
            return

    return li

exchange  是为了防止这样排序   [1,2,3,4,5,6,7,1,9]

Select Sort (选择排序)

一趟排序记录最小的数,放到第一个位置

再一趟排序记录记录列表无序区最小的数,放到第二个位置

def select_sort_simple(li):
    li_new = []
    for i in range(len(li)):
        for val in range(len(li)):
            min_val = min(li)  # 要遍历
            li_new.append(min_val)
            li.remove(min_val)  # 也是要遍历
    return li_new

min   O(n)

remove    O(n)

这个时间复杂度不是1的。

def select_sort(li):
    for i in range(len(li) - 1):  # i是第几趟
        min_loc = i
        for j in range(i, len(li)):  # 包前不包后        i 和 i +1 也行
            if li[j] < li[min_loc]:  # 判定如果小于
                min_loc = j  # 无序区最小的数
        li[i], li[min_loc] = li[min_loc], li[i]
        print('第%s趟' % i)
        print(li)

时间复杂

O(n^2)

算法关键:

有序区和无序区 

插入排序

初始时手里(有序区)只有一张牌

每次(从无序区摸一张牌,插入到手里已有牌的正确位置)

def insert_sort(li):
    for i in range(1, 2len(li)):  # 摸到牌的下标,开始就是1
        tmp = li[i]       # tmp摸到的牌的数值
        j = i - 1   # 开始手里的牌 的下标   7
        while li[j] > tmp and j >= 0:  # 找到比他小的牌就退出   7 > 6     J>=0解析    如果摸出来的是最小的数,那么j已经小于0了,所以要退出循环,并且下标为j+1的数为摸到的数,也就是我们刚刚说的数
            li[j+1] = li[j]  # 往右移
            j -= 1  # 往左移动1个单位
        li[j+1] = tmp  #


li = [2, 1, 3, 6, 5, 7, 12, 4]
insert_sort(li)
print(li)

开始说牛逼三人组了

快速排序:

 1、保证左边的比5小,右边的比5大

 2、递归完成排序  

拆开成mid,left,right

def _quick_sort(li, left, right):
    if left < right:  # 至少两个元素
        mid = partition(li, left, right)
        _quick_sort(li, left, mid-1)
        _quick_sort(li, mid+1, right)

partition 划分函数

如何划分呢??

1、先把5这个变量存起来,左边就有一个空位,留给比5小的数字,然后将这个数字放到这个位置

2、从右边开始找,找到2比5小,然后放到第一步5拿走的位置,那么右边就空了一个,那么这个空位是留给比5大的数准备的

3、继续继续继续下去

 4、重合了,那么这里就是中间了 

 

def partition(li, left, right):
    tmp = li[left]
    while left < right:
        while li[right] >= tmp and left < right:  # 从右边找比tmp小的数 ,为什么要强调left < right 是因为如果列表里面right数字都比tmp大,那么就会到最前并且跳不出循环
            right -= 1  # 往左边走一步
        li[left] = li[right]  # 把右边的数值写在左边空位上
        while left < right and li[left] <= tmp:
            left += 1  
        li[right] = li[left]                 # 把左边的数写道右边空位上
    li[left] = tmp         # 把tmp归位
    return left


def _quick_sort(li, left, right):
    if left < right:  # 至少两个元素
        mid = partition(li, left, right)
        _quick_sort(li, left, mid-1)
        _quick_sort(li, mid+1, right)

快速排序的效率:

  快速排序的时间复杂度:

 每一层的时间复杂度为O(n)

总共为O(nlogN)

快速排序的问题:

  最坏情况

  递归

堆排序:

  基础知识:

  数是一种数据结构。  比如:目录结构

  树是一种可以递归定义的数据结构

  树是由n个节点组成的集合:

    如果n = 0,那就是一颗空树

    如果n > 0, 那存在1个节点作为树的根节点,其他节点可以分为m个集合,每个集合本身又是一棵树

前期概念:

  根节点、叶子节点,树的深度、孩子节点、父节点、子树

堆排序前传:

  完全二叉树

    叶结点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树

  满二叉树 

    一个二叉树,如果每一层的节点数都达到在最大值,则这个二叉树就是满二叉树。

父节点和左孩子节点的编号下标:

2n+1

父节点和右孩子节点的编号下标:

2n+2

 

堆栈

堆排序过程:

  简单来说就是农村包围城市

 

 3不断循环,不断拿走堆顶元素,不断将最后一个元素放到堆顶

1、

取9出来,想当于3和9对调位置  ,下一个箭头指的是4了

 以此类推

堆的最后一个位置标志为head

挨个出数

def sift(li, low, high):
    """
    :param li:  列表
    :param low:    堆的根节点位置
    :param high:   堆的最后一个元素的位置
    :return:
    """
    i = low  # i是最开始指向的根节点
    j = 2 * i + 1    # j开始是左孩子
    tmp = li[low]  # 把堆顶存起来            # 广东省省长
    while j <= high:  # 只要j有数 
        if j + 1 <= high and li[j+1] > li[j]:  # 判断右孩子存在且比左孩子大
            j = j + 1  # 那么j就要指向右孩子
        if li[j] > tmp:  # 如果li[j] > tmp 那么 j要上去
            li[i] = li[j]   # 根节点跟孩子交换
            i = j    # 这里是重点,    上一层i要移到下一层的i (即时j的位置)
            j = 2 * i + 1   # j为下一层的孩子
        else:  # tmp更大了,把tmp放到i的位置上并且不用去找下一层
            li[i] = tmp   # 把tmp放到某一级上面
            break
    else:
        li[i] = tmp    # 当tmp放到叶子节点上

这个图片就是sift最后else的解析,j已经指向到左下角了,并且j的数值肯定比high大,所以跳出,tmp指向li[i]
def heap_sort(li):
    n = len(li)  # 获取列表长度 
    for i in range((n-2)//2, -1, -1):  # 从最后开始往前一个一个往前
        sift(li, i, n-1)  # 为什么是n-1呢,我们的high是拿来判定是否越界
    # 建堆完成了 !
    for i in range(n-1, -1, -1):  # 步长为-1,
        # i指向当前堆最后一个元素
        li[0], li[i] = li[i], li[0]
        sift(li, 0, i-1)  # i-1是新的High

  1、先建堆

  2、

    孩子找父亲,(n-1)/2

如图所示:

  5 为n-1,n为列表长度 

  3 为(n-1-1)//2

堆排序的复杂度:

O(nlog(n))

归并排序:

1、分解:将列表越分越小,直到分成一个元素,左右递归

2、终止条件:一个元素是有序的

3、合并:将两个有序的列表归并,列表越来越大,一个归并合成一次

[1,2,3,4,5,6,7,8,9,10]

如图所示:

NB三人组:时间复杂度都是O(nlogn)

一般情况下,就运行时间来说:

  快速排序《  归并排序  《 堆排序

三种排序算法的缺点:

  快速排序:极端情况下排序效率低

  归并排序:需要额外的内存开销 (不是原地排序)

  堆排序: 在快的排序算法中相对较慢     ()

递归是有空间消耗的

希尔排序:

  Shell 是一种分组插入排序算法。

 9//2  为4  ,过程在这里分为4个组

插入排序后:

 继续分为2组

 每组再进行插入排序

 间隔为4分为一组里面。

最后结束希尔排序

我有问题!!!

如果最后一次可以的话,为什么还要搞前面那两次呢?????并且增加了时间还多了两次

希尔排序每趟并不使某些元素有序,而是是整体数据越来越接近有序;最后一趟排序使得所有数据有序 c

第一趟

第二趟 

第三趟

 

 最后一趟

 

1.31

原文地址:https://www.cnblogs.com/jackson669/p/12207424.html