算法基础

一:什么是算法与大O表示法

算法是一组完成任务的指令。任何代码片段都可视为算法。
算法是一种通过有限过程解决问题的解决方案。

大O 表示法:
大O表示法是一种特殊的表示法,指出了算法的速度有多快;
大O表示法让你能够比较操作数,它指出了算法运行时间的增速;
大O 表示法指出了最糟情况下的运行时间。

举例,假设列表包含n个元素:
简单查找需要检查每个元素,因此需要执行n次操作。使用大O表示法,这个运行时间为 O(n)。
二分查找需要执行log n次操作。使用大O表示法,这个运行时间为 O(log n)。

 

二:算法相关数据结构介绍

1,数组

数组中的元素在内存中都是相连的(紧靠在一起的)。当增加元素时,要请求计算机重新分配一块可容纳所有元素的内存,再将所有元素都移到那里。
需要随机地读取元素时,数组的效率很高,因为可迅速找到数组的任何元素。数组随机读取某个元素的时间复杂度为O(1)。
在同一个数组中,所有元素的类型都必须相同(都为int、double等)。

#将python中的list转换为数组(list中的所有元素的类型必须相同)
import array
arr = array.array('i',[0,1,1,2,3])

2,链表

链表中的元素可存储在内存的任何地方;链表的每个元素都存储了下一个元素的地址,从而使一系列随机的内存地址串在一起。
链表的问题:无法直接读取链表的最后一个元素,因为你不知道它所处的地址,必须先访问元素#1,依次下去;跳跃取值时,效率很低。
链表的优势在插入元素和删除元素方面。

使用链表时,插入元素很简单,只需修改它前面的那个元素指向的地址。
使用数组时,则必须将后面的元素都向后移;如果没有足够的空间,可能还得将整个数组复制到其他地方!
因此,当需要在中间插入元素时,链表是更好的选择。

要删除一个元素,链表也是更好的选择,因为只需修改前一个元素指向的地址即可。而使用数组时,删除元素后,必须将后面的元素都向前移。

python实现单链表:

class Node:
	def __init__(self,data=None):
		self.data = data
		self.next = None

	def __repr__(self):
		return str(self.data)


class LinkedList:
	def __init__(self):
		self.head = None
		self.length = 0

	def is_empty(self):
		return self.length == 0

	def append(self,item):
		if isinstance(item,Node):
			this_node = item
		else:
			this_node = Node(data=item)

		if self.head == None:
			self.head = this_node
		else:
			node = self.head
			while node.next:
				node = node.next
			node.next = this_node
		self.length += 1

	def insert(self,index,item):
		if type(index) is int:
			if index < 0 or index >= self.length:
				print("Index value is out of range.")
				return
			this_node = Node(data=item)
			current_node = self.head
			if index == 0:
				self.head = this_node
				this_node.next = current_node
				return
			while index - 1:
				current_node = current_node.next
				index -= 1

			this_node.next = current_node.next
			current_node.next = this_node
			self.length += 1
			return

		else:
			print("Index value is not int.")
			return

	def delete(self,index):
		if type(index) is int:
			if index < 0 or index >= self.length:
				print("Index value is out of range.")
				return

			if index == 0:
				self.head = self.head.next
			else:
				current_node = self.head
				while index - 1:
					current_node = current_node.next
					index -= 1
				this_node = current_node.next
				current_node.next = this_node.next
				self.length -= 1
				return

		else:
			print("Index value is not int.")
			return		

	def __repr__(self):
		if self.is_empty():
			print("Linked list is empty")
		else:
			nlist = ""
			node = self.head
			nlist += "Head-->" + node.data + " "
			while node.next:
				node = node.next
				nlist += "-->" + node.data + " "

			return nlist

 

3,栈,队列,双端队列

栈 stack :一个有序的项的集合。添加项和移除项发生在同一端,即后进先出,形式如“弹夹”“叠盘子”。

队列 queue:一系列有序的元素的集合。先进先出,形式如“排队”。

双端队列 deque:一系列有序的元素的集合。允许从两端插入和从两端删除。

python实现栈:

class Stack:
	def __init__(self):
		self.items = []

	def is_empty(self):
		return self.items == []

	def peek(self):
		return self.items[-1]

	def push(self,item):
		self.items.append(item)

	def pop(self):
		return self.items.pop()
	def size(self):
		return len(self.items)

 

 

三:查找算法之二分法

二分查找只能查找有序的元素列表!这个前提条件得记住。

二分法思路:先取列表中间值与查找元素比较,若查找元素小于中间值,则在小于中间值的那一半列表中再取中间值与查找元素比较,以此类推,直到找到该元素。

一般而言,对于包含n个元素的列表,用二分查找最多需要log n步,所以二分法的时间复杂度为O(log n)。

def binary_search(list,item):
    low = 0
    high = len(list)-1

    while low <= high:
        mid = (low + high)//2
        guess = list[mid]
        if guess == item:
            return mid
        elif guess < item:
            low = mid + 1
        else:
            high = mid -1
    return None

 

四:查找算法之散列表

散列表(Hash table),也叫哈希表,是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。

散列表(hash table)是实现字典操作的一种有效的数据结构。
尽管最坏的情况下,散列表中查找一个元素的时间与链表中查找的时间相同,达到了O(n)。
然而实际应用中,散列的查找的性能是极好的。在一些合理的假设下,在散列表中查找一个元素的平均时间是O(1)。

散列函数是这样的函数,即无论你给它什么数据,它都还你一个数字(即将输入映射到数字):
1,它必须是一致的,相同的输入始终映射到同一个数字;
2,它应将不同的输入映射到不同的数字

建立散列表操作步骤:
  1) step1 取数据元素的关键字key,计算其哈希函数值(地址)。若该地址对应的存储空间还没有被占用,则将该元素存入;否则执行step2解决冲突。
  2) step2 根据选择的冲突处理方法,计算关键字key的下一个存储地址。若下一个存储地址仍被占用,则继续执行step2,直到找到能用的存储地址为止。

 

冲突处理方法:
影响哈希查找效率的一个重要因素是哈希函数本身。当两个不同的数据元素的哈希值相同时,就会发生冲突。为减少发生冲突的可能性,哈希函数应该将数据尽可能分散地映射到哈希表的每一个表项中。
解决冲突的方法有以下两种:
(1) 开放地址法
(2) 链地址法

注:Python字典dict的实现是使用开放地址法中的二次探查来解决冲突的。

 

五:排序算法之冒泡排序、选择排序、插入排序

1,冒泡排序--时间复杂度:O(n²)

冒泡排序:对一个列表多次重复遍历,比较相邻的两项,对排错的项进行交换顺序。每遍历一次,就有一个最大项排在了正确的位置。

def bubble_sort(alist):
	for passnum in range(len(alist)-1,0,-1):
		for i in range(passnum):
			if alist[i] > alist[i+1]:
				alist[i],alist[i+1] = alist[i+1],alist[i]

短路冒泡排序:冒泡排序改良版;如果在某次遍历过程中没有交换,我们就可断定列表已经排好。这样就可以使冒泡排序在已知列表排好的情况下提前结束。

def short_bubble_sort(alist):
	exchanges = True
	passnum = len(alist)-1
	while passnum > 0 and exchanges:
		exchanges = False
		for i in range(passnum):
			if alist[i] > alist[i+1]:
				exchanges = True
				alist[i],alist[i+1] = alist[i+1],alist[i]
		passnum -= 1

2,选择排序--时间复杂度:O(n²)

选择排序提高了冒泡排序的性能,它每遍历一次列表只交换一次数据,即进行一次遍历时找到最大的项,完成遍历后,再把它换到正确的位置。

#选择排序代码一
def find_smallest(arr):
    smallest = arr[0]
    smallest_index = 0
    for i in range(1,len(arr)):
        if arr[i]<smallest:
            smallest=arr[i]
            smallest_index=i
    return smallest_index

def selection_sort(arr):
    new_arr = []
    for i in range(len(arr)):
        smallest_index = find_smallest(arr)
        new_arr.append(arr.pop(smallest_index))
    return new_arr
#选择排序代码二
def selection_sort(alist):
	for passnum in range(len(alist)-1,0,-1):
		maxnum = 0
		for i in range(1,passnum+1):
			if alist[i] > alist[maxnum]:
				maxnum = i
		alist[i],alist[maxnum] = alist[maxnum],alist[i]

 

3,插入排序--时间复杂度:O(n²)

插入排序:从第二个元素开始,每个元素依次与前面的有序子列表比较,直到找到自己的位置,插入,跳出本次遍历。

def insertion_sort(alist):
	for i in range(1,len(alist)):
		for j in range(i):
			if alist[i] < alist[j]:
				alist.insert(j,alist.pop(i))
				break

 

六:什么是递归,什么是分而治之

1,递归

递归指的是调用自己的函数。

Leigh Caldwell在Stack Overflow上说:“如果使用循环,程序的性能可能更高;如果使用递归,程序可能更容易理解。如何选择要看什么对你来说更重要”。

编写递归函数时,必须告诉它何时停止递归。正因为如此,每个递归函数都有两部分:基线条件(base case)和递归条件(recursive case)。递归条件指的是函数调用自己,而基线条件则指的是函数不再调用自己,从而避免形成无限循环。

计算机在内部使用被称为调用栈的栈。通过调用栈来理解递归是一种好方法。

def greet(name):
    print "hello, " + name + "!"
    greet2(name)
    print "getting ready to say bye..."
    bye()

def greet2(name):
    print "how are you, " + name + "?"

def bye():
    print "ok bye!"

#递归函数
def fact(x):
    if x == 1:
        return 1
    else:
        return x * fact(x-1)

 

2,分而治之

分而治之(divide and conquer,D&C)—— 一种著名的递归式问题解决方法。

D&C算法是递归的。使用D&C解决问题的过程包括两个步骤。
(1) 找出基线条件,这种条件必须尽可能简单。
(2) 不断将问题分解(或者说缩小规模),直到符合基线条件。

举例:要将下面这块地均匀地分成方块,且分出的方块要尽可能大,试试用分而治之解决。

欧几里得算法:适用于这小块地的最大方块,也是适用于整块地的最大方块。
注:均匀分上面这块地的适用的最大方块为80 m× 80 m。

 

七:排序算法之快速排序、归并排序

1,快速排序

快速排序是一种常用的排序算法,比选择排序快得多。快速排序使用了D&C(分而治之)。

快速排序最佳情况也是平均情况的时间复杂度:O(nlogn);最糟情况下为 O(n²)。

快速排序思路:
(1) 选择基准值。
(2) 将数组分成两个子数组:小于基准值的元素和大于基准值的元素。
(3) 对这两个子数组进行快速排序。(递归)

def quicksort(arr):
	if len(arr)<2:
		return arr
	else:
		pivot = arr[0]
		less = [i for i in arr[1:] if i <= pivot]
		greater = [i for i in arr[1:] if i > pivot]
		return quicksort(less) + [pivot] + quicksort(greater)

 

2,归并排序

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(D&C)的一个非常典型的应用。

归并排序的时间复杂度始终为O(nlogn)!

归并排序原理:
     一、将一个序列从中间位置分成两个子序列;
     二、再将这两个子序列按照第一步继续二分下去;
     三、直到所有子序列的长度都为1,此时可以认为这些子序列是有序序列;
     四、依次两两合并有序序列,最终得到一个完整的有序序列。

def merge_list(alist,blist):
	clist = []
	i = j = 0
	while i < len(alist) and j < len(blist):
		if alist[i] < blist[j]:
			clist.append(alist[i])
			i += 1
		else:
			clist.append(blist[j])
			j += 1

	while i < len(alist):
		clist.append(alist[i])
		i += 1

	while j < len(blist):
		clist.append(blist[j])
		j += 1
	return clist

def merge_sort(tlist):
	if len(tlist) <= 1:
		return tlist

	middle = len(tlist)//2
	left = merge_sort(tlist[:middle])
	right = merge_sort(tlist[middle:])
	return merge_list(left,right)

 

八:数据结构之树、图简介

1,树

节点(Node)是树的基本构成部分。它可以有其他专属的名称,我们称之为“键(key)”。 一个节点可能有更多的信息,我们称之为“负载(payload)”。

边(Edge)也是另一个树的基本构成部分。边连接两个节点,并表示它们之间存在联系。每个节点(除了根节点)都有且只有一条与其他节点相连的入边(指向该节点的边),每个节点可能有许多条出边(从该节点指向其他节点的边)。

根节点是树中唯一一个没有入边的节点。
路径是由边连接起来的节点的有序排列。例如:动物界 → 脊索动物门 → 哺乳动物纲 → 食肉动物目 → 猫科 → 猫属 → 家猫 就是一条路径。
同一个节点的所有子节点互为兄弟节点。
没有子节点的节点成为称为叶节点。

一个节点的层数是指从根节点到该节点的路径中的边的数目。定义根节点的层数为0。
树的高度等于所有节点的层数的最大值。

树的定义一:树是节点和连接节点的边的集合。
树的定义二:一棵树有根和其他子树组成,这些子树也是树。

二叉树:每个节点最多含有两个子树的树称为二叉树

堆(heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。堆总是一棵完全二叉树。

 

2,图

图模拟一组连接;图由节点(node)和边(edge)组成。一个节点可能与众多节点直接相连,这些节点被称为邻居。

图用于模拟不同的东西是如何相连的。

有向图:其中的关系是单向的
无向图:没有箭头,直接相连的节点互为邻居

最短路径问题(shorterst-path problem)
(1) 使用图来建立问题模型。
(2) 使用广度优先搜索解决问题。

广度优先搜索是一种用于图的查找算法,可帮助回答两类问题:
第一类问题:从节点A出发,有前往节点B的路径吗?
第二类问题:从节点A出发,前往节点B的哪条路径最短?

广度优先搜索实现算法:

graph = {"rock":["mike1","mike2","mike3"],"mike1":["jack1","jack2"],"jack1":[],"jack2":[],
"mike2":["rock","mike1","mike3"],"mike3":["mike2","rock","manmm"],"manmm":[]}

def person_is_seller(name):
	return name[-1] == "m"

from collections import deque
def search(name):
	search_queue = deque()
	search_queue += graph[name]
	searched = []
	while search_queue:
		person = search_queue.popleft()
		if not person in searched:
			if person_is_seller(person):
				print(person + " is a mango seller.")
				return True
			else:
				search_queue += graph[person]
				searched.append(person)
	return False

广度优先搜索的运行时间为O(人数 + 边数),这通常写作O(V + E),其中V为顶点(vertice)数,E为边数。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值