算法和数据结构

算法:一个计算过程,解决问题的办法

递归

递归的两个必须条件

  1. 递推(调用自身)
  2. 回溯(结束条件)

来看几个例子:

eg1:该函数不是递归,没有结束条件

def func1(n):
    print(n)
    func1(n - 1)

eg2:该函数亦不是递归,虽有条件,但条件是无穷的

def func2(n):
    if n > 0:
        print(n)
        func2(n + 1)

eg3:该函数是递归,满足递归的两个条件

def func3(n):
    if n > 0:
        print(n)
        func3(n - 1)

eg4:该函数亦是递归,满足递归的两个条件

def func4(n):
    if n > 0:
        func4(n - 1)
        print(n)

那么,eg3 和 eg4的输出结果是一样的吗?如果不一样,为什么?

解释:

因为,func3执行的时候,print是在调用自身之前,所以当n是5传入函数时,会先打印5,再调用自身,这时候n是4,,依次循环递归。。。。到最后n=0,所以回溯时没有任何输出,所以func3 输出的结果是:5,4,3,2,1;而func4执行时,print是在调用自身之后,当n是5传入函数执行时,此时的n已经被减了1变成了4,再依次循环递归。。。。,再回溯的时候func4才有输出,的结果是:1,2,3,4,5

递归的简单使用,给出一个列表:[1, [2, [3, [4, [5, [6, [7, ]]]]]]],需求:拿到列表中的元素(纯数字)

# coding=utf-8
li = [1, [2, [3, [4, [5, [6, [7, ]]]]]]]


def tell(li):
    for item in li:
        if type(item) is list:
            tell(item)
        else:
            print(item)


tell(li)

列表查找

顺序查找:最常用的就是for循环,挨个对比查找,效率极低!

二分查找:把一个列表一分为二(切片),判断要找的数大于还是小于中间值,大于则在右侧查找,小于在左侧查找。每次都是将列表一切为二进行查找!

原始的二分法(切片),时间复杂度为O(n)

def find(find_num, ll):
    print(ll)
    if len(ll) == 0:
        print('not find')
        return
    mid_index = len(ll) // 2
    if find_num > ll[mid_index]:
        ll = ll[mid_index + 1:]
        find(find_num, ll)
    elif find_num < ll[mid_index]:
        ll = ll[:mid_index]
        find(find_num, ll)
    else:
        print('find', ll[mid_index])


l = [1, 3, 5, 8, 12, 34, 45, 56, 67, 78, 89, 123, 234, 345, 456, 566, 789]
find(566, l)

改进后的二分法(不用切片),时间复杂度为O(logn)

def num_search(num_list, num):
    start = 0
    end = len(num_list) - 1
    while start <= end:
        mid = (end + start) // 2
        if num_list[mid] == num:
            return mid
        elif num_list[mid] < num:
            start = mid + 1
        else:
            end = mid - 1
    return


num_l = [i for i in range(1000)]
print(num_search(num_l, 666))

排序

冒泡排序

思路:比较列表相邻的两个数,如果前边的大于后边的,就交换这两个数。。。,(升序)

代码实现,时间复杂度:O(n*n)

import random


def sort_list(list1):
    for i in range(len(list1) - 1):
        for j in range(len(list1) - i - 1):
            if list1[j] > list1[j + 1]:
                list1[j], list1[j + 1] = list1[j + 1], list1[j]
    print('遍历次数: {}'.format(i))
    return list1


data = list(range(1000))
random.shuffle(data)
print(sort_list(data))

上面的代码虽然实现了冒泡排序,但是有个效率优化的问题,假设一个极端的例子,一个列表:[1,2,3,4,5,6,7,8,9],按照上述的冒泡排序方法,程序会循环8次结束,但是在这个过程中,列表中的数字位置并未发生改变,那怎么解决这个问题呢?加一个变量来判断一次循环后数字的位置是否有改变,有就继续循环,没有就结束循环

import random


def sort_list(list1):
    for i in range(len(list1) - 1):
        change = 0
        for j in range(len(list1) - i - 1):
            if list1[j] > list1[j + 1]:
                list1[j], list1[j + 1] = list1[j + 1], list1[j]
                change = 1
        if change == 0:
            break
    print('遍历次数: {}'.format(i))
    return list1


data = list(range(1000))
random.shuffle(data)
print(sort_list(data))
改进后

选择排序

思路:循环列表,找到最小的值放到列表第一位,在遍历一次剩余数中的最小值,继续往后放。。。。

代码实现,时间复杂度:O(n*n)

def select_sort(li):
    for i in range(len(li) - 1):
        min_num = i
        for j in range(i + 1, len(li)):
            if li[j] < li[min_num]:
                min_num = j
        li[i], li[min_num] = li[min_num], li[i]


data = list(range(1000))
random.shuffle(data)
select_sort(data)
print(data)

插入排序

思路:列表分为无序区和有序区,最初的有序区只有一个值,每次从无序区选择一个值,插入到有序区的位置,直到无序区为空!

代码实现,时间复杂度:O(n*n)

def insert_sort(li):
    for i in range(1, len(li)):
        tmp = li[i]
        j = i - 1
        while j >= 0 and li[j] > tmp:
            li[j + 1] = li[j]
            j = j - 1
        li[j + 1] = tmp


data = list(range(1000))
random.shuffle(data)
insert_sort(data)
print(data)

快排

思路:一个列表先取一个元素x(第一个元素),然后使元素x归位,此时列表被元素x分为左右两半,左边都会比元素x小,右边的都会比元素x大,最后用递归完成排序!

代码实现

def quick_sort(data, left, right):
    if left < right:
        mid = partition(data, left, right)
        quick_sort(data, left, mid - 1)
        quick_sort(data, mid + 1, right)


def partition(data, left, right):
    # 用变量保存第一个元素
    tmp = data[left]
    # 左右碰不到时循环
    while left < right:
        # 找到左边比右边小的数,大于tmp的数放在右边不动
        while left < right and data[right] >= tmp:
            right -= 1
        # 将right放在左边的left空位上
        data[left] = data[right]
        # 找到左边比右边小的数,小于tmp的数放在左边不动
        while left < right and data[left] <= tmp:
            left += 1
        # 将left放在左边的right空位上
        data[right] = data[left]
    # 左右碰到时
    data[left] = tmp
    return left


data = list(range(10000))
random.shuffle(data)
quick_sort(data, 0, len(data) - 1)
print(data)

堆排序

前言

1、树与二叉树

数是一种可以递归定义的数据结构,是由N个节点组成的集合

  • 如果N=0,就是一颗空树
  • 如果N>0,那就存在1个节点作为数的根节点,其他节点可以分为M个集合,每个集合本身又是一棵树

2、两种特殊的二叉树

  • 满二叉树:最后一层的节点都是满的(a)
  • 完全二叉树:满二叉树只去掉最后一层的后面几个节点(b)

3、二叉树的存储方式

  • 链式存储
  • 顺序存储(列表)

 顺序存储就是从上往下依次按顺序存入列表,如下图:

那么,这样存储后父节点与子节点的编号下标(i)有什么关系?

  • 父节点与左子节点的编号下标关系:2i + 1
  • 父节点与右子节点的编号下标关系:2i + 2

堆排序

1、堆分为大根堆和小根堆

  • 大根堆:一颗完全二叉树,满足任一节点都要比其子节点大
  • 小根堆:一颗完全二叉树,满足任一节点都要比其子节点小

2、堆排序的过程

  1. 建立堆
  2. 得到堆顶元素,为最大元素
  3. 去掉对顶,将堆最后一个元素放在堆顶,此时可通过一次调整重新使堆变得有序
  4. 堆顶元素为第二大元素,重复步骤3,直到堆变空

3、代码实现

# coding:utf-8
import random


# 调整
def sift(arr, start, end):
    i = start
    j = 2 * i + 1
    tmp = arr[i]
    while j <= end:  # 孩子在堆里
        # 升序排序
        if j + 1 <= end and arr[j] < arr[j + 1]:  # 如果存在右孩子且大于左边的孩子
            j += 1  # j指向右孩子
        if arr[j] > tmp:  # 子比父大
            arr[i] = arr[j]  # 子放在父的空位上
            i = j  # 子成为新的父
            j = 2 * i + 1  # 新孩子
        else:
            break

    arr[i] = tmp


# 堆排序
def heap_sort(arr):
    length = len(arr)
    for i in range(length // 2 - 1, -1, -1):  # 循环每个小堆,做一次sift
        sift(arr, i, length - 1)
    # 堆建好之后,挨个出数
    for i in range(length - 1, -1, -1):  # i 指向堆的最后
        arr[0], arr[i] = arr[i], arr[0]  # 将调换下来的父节点数放在最后一位
        sift(arr, 0, i - 1)
    return arr


arr = list(range(555))
random.shuffle(arr)
print(heap_sort(arr))

归并排序

假设有一个列表,分为有序的两段,怎么让它合并为一个有序的列表

先实现一次归并,从左往右依次取左右两边的第一个元素,依次比较大小,将小的添加到新的列表中,然后继续取值比较。。。

def merge(arr, start, mid, end):
    '''
    :param arr: 一个两段有序的列表
    :param start: 左段有序列表的最小元素
    :param mid: 左段有序列表的最后一个元素
    :param end: 右段有序列表的最小元素
    '''
    tmp = []
    i = start
    j = mid + 1
    while i <= mid and j <= end:  # 两边都有数
        if arr[i] < arr[j]:  # 左边小时将左边的元素append到tmp
            tmp.append(arr[i])
            i += 1
        else:
            tmp.append(arr[j])  # 右边小时将右边的元素append到tmp
            j += 1
    while i <= mid:
        tmp.append(arr[i])
        i += 1
    while j <= end:
        tmp.append(arr[j])
        j += 1
    arr[start:end + 1] = tmp

归并的使用

分解:用递归将一个列表越分越小,直到分成一个元素,一个元素是有序的

合并:用归并将两个有序列表合并

def merge(arr, start, mid, end):
    '''
    :param arr: 一个两段有序的列表
    :param start: 左段有序列表的最小元素
    :param mid: 左段有序列表的最后一个元素
    :param end: 右段有序列表的最小元素
    '''
    tmp = []
    i = start
    j = mid + 1
    while i <= mid and j <= end:  # 两边都有数
        if arr[i] < arr[j]:  # 左边小时将左边的元素append到tmp
            tmp.append(arr[i])
            i += 1
        else:
            tmp.append(arr[j])  # 右边小时将右边的元素append到tmp
            j += 1
    while i <= mid:
        tmp.append(arr[i])
        i += 1
    while j <= end:
        tmp.append(arr[j])
        j += 1
    arr[start:end + 1] = tmp


def merge_sort(arr, start, end):
    if start < end:
        mid = (start + end) // 2
        # 分解
        merge_sort(arr, start, mid)
        merge_sort(arr, mid + 1, end)
        # 合并
        merge(arr, start, mid, end)
    return arr


arr = list(range(555))
random.shuffle(arr)
print(merge_sort(arr, 0, len(arr) - 1))

计数排序

条件:假设有一个长度为10万的列表,其中的元素都在0-100之间,将该列表内的元素进行排序

思路:先定义一个0-100的列表(下标),让每个下标对应的数全部等于0,然后循环需要排序的列表,将循环出现的数对应到定义好下标的列表中进行个数统计

代码实现

def count_sort(arr, max_num):
    count = [0 for i in range(max_num + 1)]
    for num in arr:
        count[num] += 1
    i = 0
    for num, m in enumerate(count):
        for j in range(m):
            arr[i] = num
            i += 1
    return arr


data = [random.randint(0, 100) for i in range(100)]
print('before: ', data)
print('after: ', count_sort(data, 100))

  

 

posted @ 2019-03-13 21:55  cnblogs用户  阅读(305)  评论(0编辑  收藏  举报