排序与搜索 排序与搜索 一、排序 1.冒泡排序 2.选择排序   3.插入排序   4.希尔排序 5.快速排序 6.归并排序    7.堆排序 常见排序算法效率比较 二、搜索

一、排序

排序算法(英语:Sorting algorithm)是一种能将一串数据依照特定顺序进行排列的一种算法。

排序算法的稳定性

稳定性:稳定排序算法会让原本有相等键值的纪录维持相对次序。也就是如果一个排序算法是稳定的,当有两个相等键值的纪录R和S,且在原本的列表中R出现在S之前,在排序过的列表中R也将会是在S之前。

当相等的元素是无法分辨的,比如像是整数,稳定性并不是一个问题。然而,假设以下的数对将要以他们的第一个数字来排序。

(4, 1)  (3, 1)  (3, 7)(5, 6)

在这个状况下,有可能产生两种不同的结果,一个是让相等键值的纪录维持相对的次序,而另外一个则没有:

(3, 1)  (3, 7)  (4, 1)  (5, 6)  (维持次序)--------》稳定排序((3, 1)  (3, 7)维持原有)
(3, 7) (3, 1) (4, 1) (5, 6) (次序被改变)
--------》不稳定排序(3, 1) (3, 7)没有维持原有排序)

不稳定排序算法可能会在相等的键值中改变纪录的相对次序,但是稳定排序算法从来不会如此。不稳定排序算法可以被特别地实现为稳定。作这件事情的一个方式是人工扩充键值的比较,如此在其他方面相同键值的两个对象间之比较,(比如上面的比较中加入第二个标准:第二个键值的大小)就会被决定使用在原先数据次序中的条目,当作一个同分决赛。然而,要记住这种次序通常牵涉到额外的空间负担。

1.冒泡排序

冒泡排序(英语:Bubble Sort)是一种简单的排序算法。它重复地遍历要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

冒泡排序算法的运作如下:

  • 比较相邻的元素。如果第一个比第二个大(升序),就交换他们两个。
  • 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
  • 针对所有的元素重复以上的步骤,除了最后一个。
  • 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

冒泡排序的分析

交换过程图示(第一次):

排序与搜索
排序与搜索
一、排序
1.冒泡排序
2.选择排序
 
3.插入排序
 
4.希尔排序
5.快速排序
6.归并排序
 
 7.堆排序
常见排序算法效率比较
二、搜索

那么我们需要进行n-1次冒泡过程,每次对应的比较次数如下图所示:

 排序与搜索
排序与搜索
一、排序
1.冒泡排序
2.选择排序
 
3.插入排序
 
4.希尔排序
5.快速排序
6.归并排序
 
 7.堆排序
常见排序算法效率比较
二、搜索

def bubble_sort(alist):
    for j in range(len(alist)-1,0,-1):
        # j表示每次遍历需要比较的次数,是逐渐减小的
        for i in range(j):
            if alist[i] > alist[i+1]:
                alist[i], alist[i+1] = alist[i+1], alist[i]

li = [54,26,93,17,77,31,44,55,20]
bubble_sort(li)
print(li)

时间复杂度

  • 最优时间复杂度:O(n) (表示遍历一次发现没有任何可以交换的元素,排序结束。)-----改进代码后才可以实现最优时间复杂度
  • 最坏时间复杂度:O(n2)
  • 稳定性:稳定

点击进入冒泡法代码的另一种实现方式以及冒泡法的改进

2.选择排序

选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

选择排序的主要优点与数据移动有关。如果某个元素位于正确的最终位置上,则它不会被移动。选择排序每次交换一对元素,它们当中至少有一个将被移到其最终位置上,因此对n个元素的表进行排序总共进行至多n-1次交换。在所有的完全依靠交换去移动元素的排序方法中,选择排序属于非常好的一种。

选择排序分析

排序过程:

排序与搜索
排序与搜索
一、排序
1.冒泡排序
2.选择排序
 
3.插入排序
 
4.希尔排序
5.快速排序
6.归并排序
 
 7.堆排序
常见排序算法效率比较
二、搜索

def selection_sort(alist):
    n = len(alist)
    # 需要进行n-1次选择操作
    for i in range(n-1):
        # 记录最小位置
        min_index = i
        # 从i+1位置到末尾选择出最小数据
        for j in range(i+1, n):
            if alist[j] < alist[min_index]:
                min_index = j
        alist[i], alist[min_index] = alist[min_index], alist[i]

alist = [54,226,93,17,77,31,44,55,20]
selection_sort(alist)
print(alist)

时间复杂度

  • 最优时间复杂度:O(n2)
  • 最坏时间复杂度:O(n2)
  • 稳定性:不稳定(考虑升序每次选择最大的情况)

 

3.插入排序

插入排序(英语:Insertion Sort)是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

插入排序分析

排序与搜索
排序与搜索
一、排序
1.冒泡排序
2.选择排序
 
3.插入排序
 
4.希尔排序
5.快速排序
6.归并排序
 
 7.堆排序
常见排序算法效率比较
二、搜索

def insert_sort(alist):
    # 从第二个位置,即下标为1的元素开始向前插入
    for i in range(1, len(alist)):
        # 从第i个元素开始向前比较,如果小于前一个元素,交换位置
        for j in range(i, 0, -1):#j = i [ i,i-1,i-2,i-3.....1]
            if alist[j] < alist[j-1]:
                alist[j], alist[j-1] = alist[j-1], alist[j]

def insert_sort2(alist):
    '插入排序代码另一种写法'
    n = len(alist)
    # 从右边的无序序列中取出多少个元素执行这样的过程
    for i in range(1, n):
        # i = [1, 2, 3, n-1]
        #j代表内层循环起始值
        j = i   #[j ,j-1,j-2,j-3.....1]
        #执行从右边的无序序列中取出的第一个元素,即i位置的元素,然后将其插入到前面的正确位置中
        while j > 0:
            if alist[j] < alist[j-1]:
                alist[j], alist[j-1] = alist[j-1], alist[j]
                j -= 1
            else: break# 本步对算法进行了优化
# 最优时间复杂度:O(n) (升序排列,序列已经处于升序状态)
# 最坏时间复杂度:O(n2)

if __name__=='__main__':
    alist = [54, 26, 93, 17, 77, 31, 44, 55, 20]
    insert_sort(alist)
    print(alist)
    alist = [54, 26, 93, 17, 77, 31, 44, 55, 20]
    insert_sort2(alist)
    print(alist)

  

时间复杂度

  • 最优时间复杂度:O(n) (升序排列,序列已经处于升序状态)
  • 最坏时间复杂度:O(n2)
  • 稳定性:稳定

 

4.希尔排序

希尔排序(Shell Sort)是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因DL.Shell于1959年提出而得名。 希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。

希尔排序过程

希尔排序的基本思想是:将数组列在一个表中并对列分别进行插入排序,重复这过程,不过每次用更长的列(步长更长了,列数更少了)来进行。最后整个表就只有一列了。将数组转换至表是为了更好地理解这算法,算法本身还是使用数组进行排序。

例如,假设有这样一组数[ 13 14 94 33 82 25 59 94 65 23 45 27 73 25 39 10 ],如果我们以步长为5开始进行排序,我们可以通过将这列表放在有5列的表中来更好地描述算法,这样他们就应该看起来是这样(竖着的元素是步长组成):

13 14 94 33 82
25 59 94 65 23
45 27 73 25 39
10

然后我们对每列进行排序:

10 14 73 25 23
13 27 94 33 39
25 59 94 65 82
45

将上述四行数字,依序接在一起时我们得到:[ 10 14 73 25 23 13 27 94 33 39 25 59 94 65 82 45 ]。这时10已经移至正确位置了,然后再以3为步长进行排序:

10 14 73
25 23 13
27 94 33
39 25 59
94 65 82
45

排序之后变为:

10 14 13
25 23 33
27 25 59
39 65 73
45 94 82
94

最后以1步长进行排序(此时就是简单的插入排序了)

希尔排序的分析

排序与搜索
排序与搜索
一、排序
1.冒泡排序
2.选择排序
 
3.插入排序
 
4.希尔排序
5.快速排序
6.归并排序
 
 7.堆排序
常见排序算法效率比较
二、搜索

def shell_sort1(alist):
    n = len(alist)
    # 初始步长
    gap = n // 2
    while gap > 0:#gap>=1,即gap变化到0之前,插入算法的次数
        # 按步长进行插入排序
        for i in range(gap, n):
            j = i
            # 插入排序
            while j>=gap and alist[j-gap] > alist[j]:
                alist[j-gap], alist[j] = alist[j], alist[j-gap]
                j -= gap
        # 得到新的步长
        gap = gap // 2

def shell_sort2(alist):
    '希尔排序的另一种书写方式'
    n = len(alist)
    # 初始步长
    gap = n // 2#gap值自己可调,从数学角度找到最好的gap,n//2并不是最好的,看自己计算选择出最好的步长
    # gap变化到0之前,插入算法的次数
    while gap > 0:
        #插入算法,与普通的插入算法的区别就是gap步长
        for j in range(gap, n):#以下几步和插入排序较为相似
            #j = [gap, gap+1, gap+2, gap+3,....,n-1]
            i = j
            while i>0:
                if alist[i] < alist[i-gap]:
                    alist[i-gap], alist[i] = alist[i], alist[i-gap]
                    i -= gap
                else:
                    break
        # 得缩短gap步长
        gap = gap // 2

alist = [54,26,93,17,77,31,44,55,20]
shell_sort1(alist)
print(alist)
shell_sort2(alist)
print(alist)

  

时间复杂度

  • 最优时间复杂度:根据步长序列的不同而不同
  • 最坏时间复杂度:O(n2)
  • 稳定想:不稳定

5.快速排序

快速排序(英语:Quicksort),又称划分交换排序(partition-exchange sort),通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

步骤为:

  1. 从数列中挑出一个元素,称为"基准"(pivot),
  2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
  3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会结束,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。

快速排序的分析

排序与搜索
排序与搜索
一、排序
1.冒泡排序
2.选择排序
 
3.插入排序
 
4.希尔排序
5.快速排序
6.归并排序
 
 7.堆排序
常见排序算法效率比较
二、搜索

def quick_sort(alist, start, end):
    """快速排序"""

    # 递归的退出条件
    if start >= end:
        return

    # 设定起始元素为要寻找位置的基准元素
    mid = alist[start]

    # low为序列左边的由左向右移动的游标
    low = start

    # high为序列右边的由右向左移动的游标
    high = end

    while low < high:
        # 如果low与high未重合,high指向的元素不比基准元素小,则high向左移动
        while low < high and alist[high] >= mid:
            high -= 1
        # 将high指向的元素放到low的位置上
        alist[low] = alist[high]

        # 如果low与high未重合,low指向的元素比基准元素小,则low向右移动
        while low < high and alist[low] < mid:
            low += 1
        # 将low指向的元素放到high的位置上
        alist[high] = alist[low]

    # 退出循环后,low与high重合,此时所指位置为基准元素的正确位置
    # 将基准元素放到该位置
    alist[low] = mid

    # 对基准元素左边的子序列进行快速排序
    quick_sort(alist, start, low-1)

    # 对基准元素右边的子序列进行快速排序
    quick_sort(alist, low+1, end)


alist = [54,26,93,17,77,31,44,55,20]
quick_sort(alist,0,len(alist)-1)
print(alist)

  

时间复杂度

  • 最优时间复杂度:O(nlogn)
  • 最坏时间复杂度:O(n2)
  • 稳定性:不稳定

从一开始快速排序平均需要花费O(n log n)时间的描述并不明显。但是不难观察到的是分区运算,数组的元素都会在每次循环中走访过一次,使用O(n)的时间。在使用结合(concatenation)的版本中,这项运算也是O(n)。

在最好的情况,每次我们运行一次分区,我们会把一个数列分为两个几近相等的片段。这个意思就是每次递归调用处理一半大小的数列。因此,在到达大小为一的数列前,我们只要作log n次嵌套的调用。这个意思就是调用树的深度是O(log n)。但是在同一层次结构的两个程序调用中,不会处理到原来数列的相同部分;因此,程序调用的每一层次结构总共全部仅需要O(n)的时间(每个调用有某些共同的额外耗费,但是因为在每一层次结构仅仅只有O(n)个调用,这些被归纳在O(n)系数中)。结果是这个算法仅需使用O(n log n)时间。

6.归并排序

归并排序是采用分治法的一个非常典型的应用。归并排序的思想就是先递归分解数组,再合并数组。

将数组分解最小之后,然后合并两个有序数组,基本思路是比较两个数组的最前面的数,谁小就先取谁,取了后相应的指针就往后移一位。然后再比较,直至一个数组为空,最后把另一个数组的剩余部分复制过来即可。

归并排序的分析:

排序与搜索
排序与搜索
一、排序
1.冒泡排序
2.选择排序
 
3.插入排序
 
4.希尔排序
5.快速排序
6.归并排序
 
 7.堆排序
常见排序算法效率比较
二、搜索

排序与搜索
排序与搜索
一、排序
1.冒泡排序
2.选择排序
 
3.插入排序
 
4.希尔排序
5.快速排序
6.归并排序
 
 7.堆排序
常见排序算法效率比较
二、搜索

排序与搜索
排序与搜索
一、排序
1.冒泡排序
2.选择排序
 
3.插入排序
 
4.希尔排序
5.快速排序
6.归并排序
 
 7.堆排序
常见排序算法效率比较
二、搜索

排序与搜索
排序与搜索
一、排序
1.冒泡排序
2.选择排序
 
3.插入排序
 
4.希尔排序
5.快速排序
6.归并排序
 
 7.堆排序
常见排序算法效率比较
二、搜索

排序与搜索
排序与搜索
一、排序
1.冒泡排序
2.选择排序
 
3.插入排序
 
4.希尔排序
5.快速排序
6.归并排序
 
 7.堆排序
常见排序算法效率比较
二、搜索

排序与搜索
排序与搜索
一、排序
1.冒泡排序
2.选择排序
 
3.插入排序
 
4.希尔排序
5.快速排序
6.归并排序
 
 7.堆排序
常见排序算法效率比较
二、搜索排序与搜索
排序与搜索
一、排序
1.冒泡排序
2.选择排序
 
3.插入排序
 
4.希尔排序
5.快速排序
6.归并排序
 
 7.堆排序
常见排序算法效率比较
二、搜索

def merge_sort(alist):
    if len(alist) <= 1:
        return alist
    # 二分分解
    num = len(alist)//2
    left = merge_sort(alist[:num])
    right = merge_sort(alist[num:])
    # 合并
    return merge(left,right)

def merge(left, right):
    '''合并操作,将两个有序数组left[]和right[]合并成一个大的有序数组'''
    #left与right的下标指针
    l, r = 0, 0
    result = []
    while l<len(left) and r<len(right):
        if left[l] < right[r]:
            result.append(left[l])
            l += 1
        else:
            result.append(right[r])
            r += 1
    result += left[l:]
    result += right[r:]
    return result

alist = [54,26,93,17,77,31,44,55,20]
sorted_alist = mergeSort(alist)
print(sorted_alist)

  

时间复杂度

  • 最优时间复杂度:O(nlogn)
  • 最坏时间复杂度:O(nlogn)     空间复杂度比别的排序大,因为执行完之后是新的列表,需要在新列表上保存处理(sorted_alist = mergeSort(alist)
    ),不像别的排序都是在自身排序。
  • 归并排序的效率远高于选择排序和冒泡排序
  • 稳定性:稳定

排序与搜索
排序与搜索
一、排序
1.冒泡排序
2.选择排序
 
3.插入排序
 
4.希尔排序
5.快速排序
6.归并排序
 
 7.堆排序
常见排序算法效率比较
二、搜索

排序与搜索
排序与搜索
一、排序
1.冒泡排序
2.选择排序
 
3.插入排序
 
4.希尔排序
5.快速排序
6.归并排序
 
 7.堆排序
常见排序算法效率比较
二、搜索

 

 7.堆排序

属于选择排序的一种,堆排序的思想是怎样的(以大根堆为例):

  • 首先将待排序的数组构造出一个大根堆
  • 取出这个大根堆的堆顶节点(最大值),与堆的最下最右的元素进行交换,然后把剩下的元素再构造出一个大根堆
  • 重复第二步,直到这个大根堆的长度为1,此时完成排序。
#沿左,右子节点较大者依次往下调整
def MAX_Heapify( array, HeapSize,root ):#在堆中做结构调整使得父节点的值大于子节点
    left = 2*root + 1
    right = left + 1
    larger = root
    if left < HeapSize and array[larger] < array[left]:
        larger = left
    if right < HeapSize and array[larger] <array[right]:
        larger = right
    if larger != root:#如果做了堆调整则larger的值等于左节点或者右节点的,这个时候做对调值操作
        array[larger],array[root] = array[root],array[larger]
        MAX_Heapify(array,HeapSize,larger)
 
#创建堆
def Build_MAX_Heap( array ):#构造一个堆,将堆中所有数据重新排序
    HeapSize = len( array )#将堆的长度单独拿出来方便
    for i in range( HeapSize // 2 - 1, -1, -1 ):#从后往前出数
        MAX_Heapify( array,HeapSize, i)
 
#大顶堆排序
def HeapSort( array ):#将根节点取出与最后一位做对调,对前面len-1个节点继续进行对调整过程。
    Build_MAX_Heap( array )
    #交换堆顶与最后一个结点,再调整堆
    for i in range(len(array) - 1, -1, -1 ):
        array[0], array[i] = array[i], array[0]
        MAX_Heapify(array, i, 0)
    return array
 
a = [ -3, 1, 3, 0, 9, -9, 11, 82, 7 ]
print(HeapSort(a))

时间复杂度

    • 最优时间复杂度:O(nlogn)
    • 最坏时间复杂度:O(nlogn)    
    • 稳定性:不稳定

常见排序算法效率比较

排序与搜索
排序与搜索
一、排序
1.冒泡排序
2.选择排序
 
3.插入排序
 
4.希尔排序
5.快速排序
6.归并排序
 
 7.堆排序
常见排序算法效率比较
二、搜索注:堆排序与二叉树相关。

快速排序一定要、必须的掌握,用的最多。

可以参考两篇文章,写的不错:程序员面试必备之排序算法汇总(上)程序员面试必备之排序算法汇总(下)

二、搜索

搜索是在一个项目集合中找到一个特定项目的算法过程。搜索通常的答案是真的或假的,因为该项目是否存在。 搜索的几种常见方法:顺序查找、二分法查找、二叉树查找、哈希查找

1.顺序查找 

#方式1
alist=[1,3,5,7,9,2,4,6,8,10]
x=int(input('请输入要查找的整数:'))
for i in alist:
    if i==x:
        print('找到了,整数%d在列表中。'%x)
#方式2
alist=[1,3,5,7,9,2,4,6,8,10]
x=int(input('请输入要查找的整数:'))
for i in range(len(alist)):
    if alist[i]==x:
        print('找打了,第%d个数是%d。'%(i+1,x))

2.二分法查找

二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好;其缺点是要求待查表为有序表,且插入删除困难。因此,折半查找方法适用于不经常变动而查找频繁的有序列表。首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。

 排序与搜索
排序与搜索
一、排序
1.冒泡排序
2.选择排序
 
3.插入排序
 
4.希尔排序
5.快速排序
6.归并排序
 
 7.堆排序
常见排序算法效率比较
二、搜索

二分法查找实现

(非递归实现)

def binary_search(alist, item):
    first = 0
    last = len(alist) - 1
    while first <= last:
        midpoint = (first + last) // 2
        if alist[midpoint] == item:
            return True
        elif item < alist[midpoint]:
            last = midpoint - 1
        else:
            first = midpoint + 1
    return False

testlist = [0, 1, 2, 8, 13, 17, 19, 32, 42, ]
print(binary_search(testlist, 3))
print(binary_search(testlist, 13))

  

(递归实现)

def binary_search(alist, item):
    if len(alist) == 0:
        return False

    midpoint = len(alist) // 2
    if alist[midpoint] == item:
        return True
    elif item < alist[midpoint]:
        return binary_search(alist[:midpoint], item)
    else:
        return binary_search(alist[midpoint + 1:], item)


testlist = [0, 1, 2, 8, 13, 17, 19, 32, 42, ]
print(binary_search(testlist, 3))
print(binary_search(testlist, 13))

时间复杂度

  • 最优时间复杂度:O(1)
  • 最坏时间复杂度:O(logn)

 顺序查找的最坏时间复杂度是O(n),最优时间复杂度是O(1).所以二分查找算法使得搜索得到了优化。

3.Hash查找

  算法简介哈希表就是一种以键-值(key-indexed) 存储数据的结构,只要输入待查找的值即key,即可查找到其对应的值。

   排序与搜索
排序与搜索
一、排序
1.冒泡排序
2.选择排序
 
3.插入排序
 
4.希尔排序
5.快速排序
6.归并排序
 
 7.堆排序
常见排序算法效率比较
二、搜索

  排序与搜索
排序与搜索
一、排序
1.冒泡排序
2.选择排序
 
3.插入排序
 
4.希尔排序
5.快速排序
6.归并排序
 
 7.堆排序
常见排序算法效率比较
二、搜索

排序与搜索
排序与搜索
一、排序
1.冒泡排序
2.选择排序
 
3.插入排序
 
4.希尔排序
5.快速排序
6.归并排序
 
 7.堆排序
常见排序算法效率比较
二、搜索

  算法思想哈希的思路很简单,如果所有的键都是整数,那么就可以使用一个简单的无序数组来实现:将键作为索引,值即为其对应的值,这样就可以快速访问任意键的值。这是对于简单的键的情况,我们将其扩展到可以处理更加复杂的类型的键。

  算法流程1)用给定的哈希函数构造哈希表;
       2)根据选择的冲突处理方法解决地址冲突;常见的解决冲突的方法:拉链法和线性探测法。
       3)在哈希表的基础上执行哈希查找。

  复杂度分析单纯论查找复杂度:对于无冲突的Hash表而言,查找复杂度为O(1)(注意,在查找之前我们需要构建相应的Hash表)。

  算法实现

# 忽略了对数据类型,元素溢出等问题的判断。
 
class HashTable:
  def __init__(self, size):
    self.elem = [None for i in range(size)] # 使用list数据结构作为哈希表元素保存方法
    self.count = size # 最大表长
 
  def hash(self, key):
    return key % self.count # 散列函数采用除留余数法
 
  def insert_hash(self, key):
    """插入关键字到哈希表内"""
    address = self.hash(key) # 求散列地址
    while self.elem[address]: # 当前位置已经有数据了,发生冲突。
      address = (address+1) % self.count # 线性探测下一地址是否可用
    self.elem[address] = key # 没有冲突则直接保存。
 
  def search_hash(self, key):
    """查找关键字,返回布尔值"""
    star = address = self.hash(key)
    while self.elem[address] != key:
      address = (address + 1) % self.count
      if not self.elem[address] or address == star: # 说明没找到或者循环到了开始的位置
        return False
    return True
 
 
if __name__ == '__main__':
  list_a = [12, 67, 56, 16, 25, 37, 22, 29, 15, 47, 48, 34]
  hash_table = HashTable(12)
  for i in list_a:
    hash_table.insert_hash(i)
 
  for i in hash_table.elem:
    if i:
      print((i, hash_table.elem.index(i)), end=" ")
  print("
")
 
  print(hash_table.search_hash(15))
  print(hash_table.search_hash(33))