1.求1+2+3+......+100
循环累加
package main
import "fmt"
func main() {
sum, n := 0, 100
for i:=1;i<=n;i++ {
sum = sum + i
}
fmt.Printf("%d", sum)
}
高斯算法求和
相当于等差数列求和
package main
import "fmt"
func main() {
sum, n := 0, 100
sum = (1 + n) * n / 2
fmt.Printf("%d", sum)
}
2.7 算法效率的度量方法
事后统计方法
事前分析估算方法
package main
import "fmt"
func main() {
x, sum, n := 0, 0, 100 //执行1次
for i := 1; i <= n; i++ {
for j := 1; j <= n; j++ {
x++ //执行1次
sum = sum + x
}
}
fmt.Printf("%d", sum) //执行1次
}
2.8 函数的渐近增长
2.9 算法时间复杂度
我们的三个求和算法的时间复杂度分别为O(n),O(1),O(n^2)。O(1)叫常数阶,O(1)叫线性阶,O(n^2)叫平方阶。
2.9.2 推导大O阶方法
2.9.3 常数阶
2.9.4 线性阶
2.9.5 对数阶
下面这对代码,时间复杂度是多少?
package main
import "fmt"
func main() {
count, n := 1, 100
for count < n {
count = count * 2
//时间复杂度为O(1)的程序步骤序列
}
}
2^x=n,x=log2 n真数,所以这个循环的时间复杂度是O(logn)。
2.9.6 平方阶
2.10 常见的时间复杂度
2.11 最坏情况和平均情况
2.12 算法空间时间复杂度
要判断某某年是不是闰年
判断一个年份是否是闰年,需要满足下面条件之一:
年份能被4整除,但不能被100整除;
能被400整除
package main
import (
"fmt"
)
func main() {
var year int
fmt.println("请输入年份:")
fmt.Scanln(&year)
if (year%4==0 &&year%100!=0) || year%400==0 {
fmt.println(year, "是闰年。")
} else {
fmt.println(year, "不是闰年。")
}
}
2.13 总结回顾
第3章 线性表
要实现两个线性表集合A和B的并集操作
//两个集合取并集
package main
import "fmt"
//思想:
//运用map,统计nums1中值出现的次数,即map[值]次数
//遍历nums2中的值,查看值是否在map中的出现
func union(num1, num2 []string) []string {
m := make(map[string]int)
for _, v := range num1 {
m[v]++
}
fmt.Println(m)
for _, v := range num2 {
// times, bool := m[v]
// if bool != true {
// fmt.Println("不存在")
// }
times, _ := m[v]
fmt.Printf("v=%s,times=%d\n", v, times)
if times == 0 {
num1 = append(num1, v)
}
}
return num1
}
func main() {
num1 := []string{"3", "4", "1"}
num2 := []string{"2", "1", "3"}
fmt.Println(union(num1,num2))
}
package model
import (
"sort"
"sync"
)
type Set struct {
sync.RWMutex
m map[int]bool
}
// 新建集合对象
func New(items ...int) *Set {
s := &Set{
m: make(map[int]bool, len(items)),
}
s.Add(items...)
return s
}
// 添加元素
func (s *Set) Add(items ...int) {
s.Lock()
defer s.Unlock()
for _, v := range items {
s.m[v] = true
}
}
// 删除元素
func (s *Set) Remove(items ...int) {
s.Lock()
defer s.Unlock()
for _, v := range items {
delete(s.m, v)
}
}
// 判断元素是否存在
func (s *Set) Has(items ...int) bool {
s.RLock()
defer s.RUnlock()
for _, v := range items {
if _, ok := s.m[v]; !ok {
return false
}
}
return true
}
// 元素个数
func (s *Set) Count() int {
return len(s.m)
}
// 清空集合
func (s *Set) Clear() {
s.Lock()
defer s.Unlock()
s.m = map[int]bool{}
}
// 空集合判断
func (s *Set) Empty() bool {
return len(s.m) == 0
}
// 无序列表
func (s *Set) List() []int {
s.RLock()
defer s.RUnlock()
list := make([]int, 0, len(s.m))
for item := range s.m {
list = append(list, item)
}
return list
}
// 排序列表
func (s *Set) SortList() []int {
s.RLock()
defer s.RUnlock()
list := make([]int, 0, len(s.m))
for item := range s.m {
list = append(list, item)
}
sort.Ints(list)
return list
}
// 并集
func (s *Set) Union(sets ...*Set) *Set {
r := New(s.List()...)
for _, set := range sets {
for e := range set.m {
r.m[e] = true
}
}
return r
}
// 差集
func (s *Set) Minus(sets ...*Set) *Set {
r := New(s.List()...)
for _, set := range sets {
for e := range set.m {
if _, ok := s.m[e]; ok {
delete(r.m, e)
}
}
}
return r
}
// 交集
func (s *Set) Intersect(sets ...*Set) *Set {
r := New(s.List()...)
for _, set := range sets {
for e := range s.m {
if _, ok := set.m[e]; !ok {
delete(r.m, e)
}
}
}
return r
}
// 补集
func (s *Set) Complement(full *Set) *Set {
r := New()
for e := range full.m {
if _, ok := s.m[e]; !ok {
r.Add(e)
}
}
return r
}
3.4 线性表的顺序存储结构
3.5 顺序存储结构的插入与删除
3.5.1 获得元素操作
package main
import (
"errors"
)
type ElemType int
const MAXSIZE = 20
type SqList struct {
data [MAXSIZE] ElemType
length int
}
//获取数据元素
func GetElem(s SqList, i int) (err error, res ElemType) {
if s.length == 0 || i < 0 || i > s.length {
err = errors.New("查找失败")
}
res = s.data[i]
return
}
3.5.2 插入操作
//插入数据
func ListInsert(s *SqList, i int, e *ElemType) error{
if s.length == MAXSIZE {
return errors.New("线性表已满,不能插入数据")
}
if i < 0 || i > s.length {
return errors.New("插入的位置不正确")
}
//i位开始后移一位
//从最后一位向前遍历到第i位
for j := s.length; j>=i; j-- {
s.data[j+1] = s.data[j]
}
s.data[i] = *e
s.length++
return nil
}
3.5.3 删除操作
//删除数据
func ListDelete(s SqList, i int) (err error) {
if s.length == 0 {
err = errors.New("线性表为空")
}
if i < 0 || i > s.length-1 {
err = errors.New("删除的位置不正确")
}
//从删除元素位置开始向后遍历到最后一个位置,分别将他们向前移动一位
for j := i; j < s.length-1; j++ {
s.data[j] = s.data[j+1]
}
s.data[s.length-1] = 0 //清除数据
s.length--
return
}
3.5.4 线性表顺序存储结构的优缺点
3.6 线性表的链式存储结构
单链表,可用结构指针来描述
//链表结点
type ListNode struct {
value int //定义数据域
next *ListNode //定义指针域
}
//单链表
type LinkList struct {
// 头结点还是头指针?头结点可以包含指针域,有或者没有数据域
head *ListNode
// 长度
length int
}
3.7 单链表的读取
获取链表第i个数据的算法思路:
1.声明一个结点p指向链表第一个元素,初始化j从1开始;
2.当j
3.若到链表末尾p为空,则说明第i个元素不存在;
4.否则查找成功,返回结点p的数据。
//初始条件:顺序线性表L已存在,1<=i<=L.length-1
//操作结果:用p返回L中第i个数据元素的值
func (list *LinkList) GetNodeAtIndex(i int) *Node{
//声明一个结点p,让p指向链表list的头结点
var p *Node = list.head
for p!=nil {
//找到该数据所在结点
if(i == p.value){
return p
} else {
p = p.next
}
return nil
/* // 索引越界
if i > list.length-1 || index < 0 {
return nil
}
// 备份头结点
bak := list.head
// 向后循环
for index > -1 {
bak = bak.pNext
index--
}
return bak
*/
}
3.8 单链表的插入与删除
单链表第i个数据插入结点的算法思路:
1.声明一结点p指向链表第一个结点,初始化j从1开始;
2.当j
3.若到链表末尾p为空,则说明第i个元素不存在;
4.否则查找成功,在系统中生成一个空结点s;
5.将数据元素e赋值给s.value;
6.单链表的插入标准语句s.next=p.next p.next=s
7.返回成功
func(list *LinkList) insertNodeValueFront(node *LinkNode, i int) bool {
//备份头结点
p := list.head
//循环到末尾,直到找到目标结点
for p.next !=nil && p.next.value != i {
p = p.next
}
//找到指定元素i
if p.next.value == i {
//
node.next = p.next
//
p.next = node
list.length++
return true
}
return false
}
3.8.2 单链表的删除
单链表第i个数据删除结点的算法思路:
1.声明一结点p指向链表第一个结点,初始化j从1开始;
2.当j
3.若到链表末尾p为空,则说明第i个元素不存在;
4.否则查找成功,将欲删除的结点p->next赋值给q;
5.单链表的删除标准语句p->next=q->next;
6.将q结点中的数据赋值给e,作为返回;
7.释放q结点;
8.返回成功。
实现代码算法:
//操作结果:删除L的第i个数据元素,删除成功返回true,L的长度减1
func(list *LinkList) deleteNode(node *LinkNode, i int) bool {
//备份头结点
p := list.head
//循环到末尾,直到找到目标结点
for p.next !=nil && p.next.value != i {
p = p.next
}
//找到指定结点/元素i
if p.next.value == i {
//
node = p.next
//
p.next = node.next
list.length--
return true
}
return false
}
单链表插入和删除算法,它们其实都是由两部分组成:第一部分就是遍历查找第i个元素;第二部分就是插入和删除元素。整体复杂度都是O(n)。显然,对于插入和删除数据约频繁的操作,单链表的效率优势就越是明显。
3.9 单链表的整表创建
单链表整表创建的算法思路:
1.声明一结点p和计数器变量i;
2.初始化一空链表L;
3.让L的头节点的指针指向NULL,即建立一个带头结点的单链表;
4.循环:
生成一新节点赋值给p;
随机生成一数字赋值给p的数据域p->data;
将p插入到头结点与前一新结点之间。
实现代码算法:
//随机产生n个元素的值,建立带表头结点的单链线性表L(头插法)
golang构造单链表
package main
import "fmt"
type ListNode struct {
value int
next *ListNode
}
type LinkList struct {
head *ListNode
}
func main() {
fmt.Println("链表长度为:",list.getLength())
fmt.Println("链表是否为空:",list.isEmpty())
fmt.Print("遍历链表:")
var q *ListNode=list.head
for q!=nil{
fmt.Print(q.value," ")
q=q.next
}
fmt.Println()
}
func createListHead(list *LinkList, n int) {
var p *ListNode
//初始化随机种子
//fmt.println(time.Now().Unix())
/* fmt.Println(time.Now().UnixNano())
r := rand.New(rand.NewSource(time.Now().UnixNano()))
for i := 0; i < 10; i++ {
fmt.Println(r.Intn(100))
}
*/
rand.Seed(time.Now().UnixNano())
//先建立一个带头结点的单链表
list.head.next = nil
for(i:=0; i
//随机产生n个元素的值,建立带表头结点的单链线性表L(尾插法)
func createListTail(list *LinkList, n int){
var p *LinkNode
var r *LinkNode
rand.Seed(time.New().UnixNano())
r = list //r为指向尾部的结点
for(i:=0; i
3.10单链表的整表删除
单链表整表删除的算法思路如下:
1.声明一结点p和q;
2.将第一个结点赋值给p;
3.循环:
将下一个结点赋值给q;
释放p;
将q赋值给p。
实现代码算法如下:
//初始条件:顺序线性表L已存在,操作结果:将L重置为空表
func clearList(list *LinkList) {
var p,q *LinkNode
//p指向第一个结点
p = list.head
for (p.next != nil) {//循环到末尾
q = p.next
p = nil
p = q
}
//头结点指针域为空
list.head.next = nil
return true
}
3.11 单链表结构与顺序存储结构优缺点
存储分配方式:顺序存储结构用一段连续的存储单元依次存储线性表的数据元素
单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素
时间性能:
查找:顺序存储结构O(1),单链表O(n)
插入和删除:
顺序存储结构需要平均移动表一半的元素,时间为O(n)
单链表在线出某位置的指针后,插入和删除时仅为O(1)
空间性能
顺序存储结构需要预分配存储空间,分大了,浪费,分小了易发生上溢
单链表不需要分配存储空间,只要有就可以分配,元素个数也不受限制
3.12 静态链表
用数组来代替指针,来描述单链表。我们让数组的元素都是由两个数据域组成,data和cur。
我们把这种用数组描述的链表叫做静态链表,这种描述方法叫做游标实现法。
//线性表的静态链表存储结构
//静态链表结点
type ListNode struct {
data string
cursor int
}
const maxSize int = 10
//初始化链表
func initList(list *StaticLinkList){
var i int
for(i=0;i
3.12.1 静态链表的插入操作
静态链表中要解决的是:如何用静态模拟动态链表结构的存储空间的分配,需要时申请,无用时释放。
为了辨明数组中哪些分量未被使用,解决的办法是将所有未被使用过的及已被删除的分量用游标链成一个备用的链表,每当进行插入时,便可以从备用链表上取得第一个结点作为待插入的新结点。
//若备用空间链表非空,则返回分配的结点下标,否则返回0
func malloc(list *StaticLinkList) int{
i := list[0].cursor //当前数组第一个元素的cur存的值,就是要返回的第一个备用空间的下标
// if i == 0 {
// os.Exit(0)
//}
if (list[0].cursor) {
list[0].cursor = list[i].cursor //由于要拿出一个分量来使用了,所以我们就得把它的下一个分量用来做备用
}
return i
}
//在list中第i个元素之前插入新的数据元素e
func listInsert(list *StaticLinkList, i int, data string) {
var j,k,l int
k = maxSize - 1
if (i < 1 || i > length(list))
return false
j = malloc(list) //获得空闲分量的下标
if(j){
list[j].data = data //将数据赋值给此分量的data
for (l = 1;l<=i-1;i++){//找到第i个元素之前的位置
k = list[k].cursor
}
list[j].cursor = list[k].cursor//把第i个元素之前的cur赋值给新元素的cur
list[k].cursor = j //把新元素的下标赋值给第i个元素之前的cur
return true
}
return false
}
3.12.2 静态链表的删除操作
//删除在L中第i个元素
func listDelete(list []Node, i int) {
var j, k int
if (i <1 || i > length(list))
return false
k = maxSize -1
for (j = 1;j<= i-1;j++){
k = list[k].cursor
}
j = list[k].cursor
list[k].cursor = list[j].cursor
free(list, j)
return true
}
//将下标为k的空闲结点回收到备用链表
func free(list *StaticLinkList, k int) {
list[k].cursor = list[0].cursor //把第一个元素cur值赋给要删除的分量cursor
list[0].cursor = k //把要删除的分量下标赋值给第一个元素的cursor
}
//初始条件:静态链表L已存在。操作结果:返回L中数据元素个数
func listLength(list []Node) {
j := 0
i := list[maxSize-1].cursor
if(i) {
i = list[i].cursor
j++
}
return j
}
3.12.3 静态链表优缺点
优点:在插入和删除操作时,只需要修改游标,不需要移动元素,从而改进了在顺序存储结构中的插入和删除操作需要移动大量元素的缺点。
缺点:没有解决连续存储分配带来的表长难以确定的问题。
失去了顺序存储结构随机存取的特性。
总之,静态链表是为了给没有指针的高级语言设计的一种实现单链表能力的方法。
3.13 循环链表
将单链表中终端结点的指针端由空指针改为指向头结点,就使用整个单链表形成一个环,这种头尾相接的单链表成为单循环链表,简称循环链表。
其实循环链表和单链表的主要差异就在于循环的判断条件上,原来是判断p->next是否为空,现在则是p->next不等于头结点,则循环结束。
要将两个循环链表合并成一个表时,有了尾指针就非常简单了。
p=rearA.next //保存A表的头结点
rearA.next= rearB.next.next //将本是指向B表的第一个结点(不是头结点)赋值给rearA.next
rearB.next=p //将原A表的头结点赋值给rearB.next
p = nil //释放p
3.14 双向链表
双向链表是在单链表的每个结点中,再设置一个指向其前驱结点的指针域。
//线性表的双向链表存储结构
type dulLinkNode struct {
data string
prior *dulLinkNode
next *dulLinkNode
}
type dulLinkList struct {
head *dulLinkNode
tail *dulLinkNode
}
既然单链表也可以有循环链表,那么双向链表当然也可以是循环表。
由于这是双向链表,那么对于链表中的某一个结点p,它的后继的前驱是谁?当然还是它自己。它的前驱的后继自然也是它自己,即:
p.next.prior=p=p.prior.next
插入操作,插入结点s
s.prior = p // 把p赋值给s的前驱
s.next = p.next //把p.next赋值给s的后继
p.next.prior = s// 把s赋值给p.next的前驱
p.next = s//把s赋值给p的后继
顺序是先搞定s的前驱和后继,再搞定后结点的前驱,最后解决前结点的后继。
删除操作,删除结点p
p.prior.next = p.next //把p.next赋值给p.prior的后继\
p.next.prior = p.prior//把p.prior赋值给p.next的前驱
p = nil//释放结点
3.15 总结回顾
线性表是零个或多个具有相同类型的数据元素的有序序列。
顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表的数据元素。通常我们都是用数组来实现这一结构。
由于顺序存储结构的插入和删除操作不方便,引出了链式存储结构。它具有不受固定的存储空间限制,可以比较快捷的插入和删除操作的特点。然后我们分别就链式存储结构的不同形式,如单链表、循环链表和双向链表做了讲解,还讲了不使用指针如何处理链表结构的静态链表方法。
第4章 栈与队列
栈是限定仅在表尾进行插入和删除操作的线性表。
队列是只允许在一端进行插入操作、而在另一端进行删除操作的线性表。
栈 data 同线性表。元素具有相同的类型,相邻元素具有前驱和后继的关系。
操作
initStack(*S):初始化操作,建立一个空栈S。
destroryStack(*S):若栈存在,则销毁它。
clearStack(*S):将栈清空。
stackEmpty(S):若栈为空,返回true,否则返回false。
getTop(S, *e):若栈存在且非空,用e返回S的栈顶元素。
push(*S, e):若栈S存在,插入新元素e到栈S中并成为栈顶元素。
pop(*S, *e):删除栈S中栈顶元素,并用e返回其值。
stackLength(S):返回栈S的元素个数。
4.4 栈的顺序存储结构及实现
4.4.1 栈的顺序存储结构
若存储栈的长度为stackSize,则栈顶位置top必须小于stackSize。当栈存在一个元素时,top等于0,因此通常把空栈的判定条件定位top等于-1。
type stack struct {
maxSize int //规定栈最多放几个元素
arr [5]int //用数组模拟栈
top int //用于栈顶指针,目前栈顶的下标
}
4.4.2 栈的顺序存储结构--进栈操作
//插入元素data为新的栈顶元素
func push(*S stack, e string) {
if (S.top == maxSize-1){//栈满
return false
}
S.top++ //栈顶指针增加一
S.data[S.top] = e // 将新插入元素赋值给栈顶空间
return true
}
4.4.3 栈的顺序存储结构--出栈操作
出栈操作pop,代码如下:
//若栈不空,则删除S的栈顶元素,用e返回其值,并返回true,否则返回false
func pop(*S stack, *e string) {
if (S.top == -1) {
return false
}
*e = S.data[S.top] //将要删除的栈顶元素赋值给e
S.top-- //栈顶指针减一
return true
}
4.5 两栈共享空间
数组有两个端点,两个栈有两个栈底,让一个栈的栈底为数组的始端,即下标为0处,另一个栈为栈的末端,即下标为数组长度n-1处。这样,两个栈如果增加元素,就是两端向中间延伸。
其实关键思路是:它们是在数组的两端,向中间靠拢。top1和top2是栈1和栈2的栈顶指针,可以想象,只要它们俩不见面,两个栈就可以一直使用。
//两栈共享空间结构
type doubeStack struct {
maxSize int
arr [20]int
top1 int //栈1栈顶指针
top2 int //栈2栈顶指针
}
对于两栈共享空间的push方法,除了要插入元素值参数外,还需要有一个判断是栈1还是栈2的栈号参数stackNumber。
//插入元数e为新的栈顶元素
func(this *doubleStack) push(e int, stackNumber int) {
//栈已满,不能再push新元素了
if(doubleStack.top1+1 == doubleStack.top2) {
return false
}
//栈1有元素进栈
if(stackNumber==1) {
//若栈1则先top1+1后给数组元素赋值
top1 = top1+1
S.data = e
} else if(stackNumber==2) {//栈2有元素进栈
//若栈1则先top2-1后给数组元素赋值
top2 = top2-1
S.data = e
}
return true
}
对于两栈共享空间的pop方法,参数就只是判断栈1栈2的参数stackNumber,代码如下:、
//若栈不空,则删除S的栈顶元素,用e返回其值,并返回true,否则返回false
func (*S doubleStack) pop(e int, stackNumber int) {
if(stackNumber==1){
if(S.top1==-1){//说明栈1已经是空栈,溢出
return false
}
e = S.data //将栈1的栈顶元素出栈
S.top1 = S.top1-1
} else if(stackNumber==2) {
if(S.top2==maxSize){//说明栈2已经是空栈,溢出
return false
}
e = S.data //将栈2的栈顶元素出栈
S.top2 = S.top2+1
}
return true
}
使用这样的数据结构,通常都是当两个栈的空间需求有相反关系时,也就是一个栈增长时另一个栈在缩短的情况。
4.6 栈的链式存储结构及实现
4.6.1 栈的链式存储结构
栈的链式存储结构,简称链栈。
对于空栈来说,链表原定义是头指针指向空,那么链栈的空其实是就是top=nil的时候。
type stackNode struct {
data interface{}
next *stackNode
}
type linkStackPtr *stackNode
type linkStack struct {
top linkStackPtr
count int
}
4.6.2 栈的链式存储结构--进栈操作
//插入元素e为新的栈顶元素
func (S *linkStack) push(e interface{}){
//创建新结点s
var s stackNode
s.data = e
s.next = S.top //把当前的栈顶元素赋值给新结点的直接后继
S.top = s//将新的结点S赋值给栈顶指针
S.count++
return true
}
4.6.3 栈的链式存储结构--出栈操作
//若栈不空,则删除S的栈顶元素,用e返回其值,并返回true,否则返回false
func(S *linkStack) pop(e interface{}) {
//var p linkStackPtr
var p *stackNode
if (stackEmpty(S)){
return true
}
e = S.top.data
p = S.top //将栈顶结点赋值给p
S.top = S.top.next //使得栈顶指针下移一位,指向后一结点
//释放结点p
p = nil
S.count--
return true
}
如果栈的使用过程中元素变化不可预料,有时很小,有时非常大,那么最好是用链栈,反之,如果它的变化在可控范围内,建议使用顺序栈会更好一些。
4.7 栈的作用
4.8 栈的应用--递归
4.8.1 斐波那契数列的实现
这个数列有个十分明显的特点,那是:前面相邻两项之和,构成了后一项。
用常规的迭代实现,打印出前40位的斐波那契数列数
func main() {
var i int
var a[40] int
a[0] = 0
a[1] = 1
fmt.Printf("%d", a[0])
fmt.Printf("%d", a[1])
for(i = 2;i < 40;i++){
a[i] = a[i-1] + a[i-2]
fmt.Printf("%d", a[i])
}
return 0
}
用递归来实现
//斐波那契的递归函数
func fbi(i int) {
if(i == 0) {
return 0
} else if(i == 1){
return 1
} else {
return fni(i-1) + fni(i-2) // 这里fbi就是函数自己,它在调用自己
}
}
func main(){
var i int
for (i=0;i<40;i++) {
fmt.Printf("%d", fbi(i))
}
return 0
}
4.8.2 递归定义
我们把一个直接调用自己或通过一系列的调用语句间接地调用自己的函数,称为递归函数。
每个递归定义必须至少有一个条件,满足时递归不再进行,即不再引用自身而是返回值退出。
迭代使用的是循环结构,递归使用的是选择结构。
大量的递归调用会建立函数的副本,会消耗大量的时间和内存。迭代则不需要反复调用函数和占用额外的内存。
4.9 栈的应用--四则运算表达式求值
4.9.1 后缀(逆波兰)表示法定义
4.9.3 中缀表达式转后缀表达式
要想让计算机具有处理我们通常的标准(中缀)表达式的能力,最重要的就是两步:
1.将中缀表达式转化为后缀表达式(栈用来进出运算的符合)。
2.将后缀表达式进行运算得出结果(栈用来进出运算的数字)。
4.10 队列的定义
队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
4.11 队列的抽象数据类型
队列(queue)
data
同线性表。元素具有相同的类型,相邻元素具有前驱和后继关系。
operation
initQueue(*Q): 初始化操作,建立一个空队列Q
destroyQueue(*Q): 若队列Q存在,则销毁它。
clearQueue(*Q): 将队列Q清空
queueEmpty(Q): 若队列Q为空,返回true,否则返回false。
getHead(Q,*e): 若队列Q存在且非空,用e返回队列Q的队头元素。
enQueue(*Q, e): 若队列Q存在,插入新元素e到队列Q中并成为队尾元素。
deQueue(*Q, *e): 删除队列Q中队头元素,并用e返回其值。
queueLength(Q): 返回队列Q的元素个数
4.12 循环队列
线性表有顺序存储和链式存储,栈是线性表,所以有这两种存储方式。
为了避免当只有一个元素时,队头和队尾重合使处理变得麻烦,所以引入两个指针,front指针指向队头元素,rear指针指向队尾元素的下一个位置,这样当front等于rear时,此队列不是还剩一个元素,而是空队列。
4.12.2 循环队列定义
我们把队列的这种头尾相接的顺序存储结构称为循环队列。
队列满的条件是(rear+1)%queueSize == front
通用的计算队列长度公式为:
(rear-front+queueSize)%queueSize
循环队列的顺序存储结构代码如下:
//循环队列的顺序存储结构
type sqQueue struct {
data []int
front int //头指针
rear int // 尾指针,若队列不空,指向队列尾元素的下一个位置
}
循环队列的初始化代码如下:
//初始化一个空队列Q
func initQueue() *sqQueue{
Q := new(sqQueue)
Q.data = make([]int, maxSize)
Q.front = 0
Q.rear = 0
return Q
}
循环队列求队列长度代码如下:
//返回Q的元素个数,也就是队列的当前长度
func queueLength(Q sqQueue){
return (Q.rear-Q.front+maxSize)%maxSize
}
循环队列的入队列操作代码如下:
//若队列未满,则插入元素e为Q新的队尾元素
func enQueue(*Q sqQueue, e int){
if((Q.rear+1)%maxSize == Q.front){//队列满的判断
return false
}
//将元素e赋值给队尾
Q.data[Q.rear] = e
Q.rear = (Q.rear+1)%maxSize //rear指针向后移一位置
//若到最后则转到数组头部
return true
}
循环队列的出队列操作代码如下:
//若队列不空,则删除Q中队头元素,用e返回其值
func(Q *sqQueue) deQueue() int {
if(Q.front == Q.rear) {//队列空的判断
return nil
}
e := Q.data[Q.front]
Q.data[Q.front] = nil
Q.front = (Q.front+1)%maxSize //front指针向后移一位置,若到最后则转到数组头部
return e
}
4.13 队列的链式存储结构及实现
队列的链式存储结构,其实就是线性表的单链表,只不过它只能尾进头出而已,我们把它简称为链队列。
链队列的结构为:
type qNode struct {
data int
next *qNode
}
type linkQueue struct {
//队头、队尾指针
front *qNode
rear *qNode
count int //所属队列
}
4.13.1 队列的链式存储结构--入队操作
//插入元素e为Q的新的队尾元素
func(lsQueue *linkQueue) enQueue(e int) *qNode {
node := new(qNode)
node.data = e
node.next = nil
//node.lsQueue = lsQueue
lsQueue.rear.next = node //把拥有元素e新结点node赋值给原队尾结点的后继
lsQueue.rear = node // 把当前的node设置为队尾结点,rear指向node
lsQueue.count++
return node
}
4.13.2 队列的链式存储结构--出队操作
//若队列不空,删除Q的队头元素,用e返回其值,并返回true,否则返回nil
func(lsQueue *linkQueue) deQueue(e int) *qNode{
if lsQueue.front == lsQueue.rear {
return nil
}
//将欲删除的队头结点暂存给node
node := lsQueue.front.next
e = node.data //将欲删除的队头结点的值赋值给e
lsQueue.front.next = node.next //将原队头结点后继node.next赋值给头结点后继
//若队头是队尾,则删除后将rear指向头结点
if(lsQueue.rear == node) {
lsQueue.rear = lsQueue.front
}
return node
}
总的来说,在可以确定队列长度最大值的情况下,建议用循环队列,如果你无法预估队列的长度时,则用链队列。
4.14 总结回顾
4.15 结尾语
第5章 串
串是由零个或多个字符组成的有限序列,又名叫字符串。
串(string)
Data
串中元素仅由一个字符组成,相邻元素具有前驱和后继关系
Operation
strAssign(T, *chars):生成一个其值等于字符串常量chars的串T
strCopy(T,S):串S存在,由串S复制得串T
clearString(S):串S存在,将串清空
stringEmpty(S):若串S为空,返回true,否则返回false
strLength(S):返回串S的元素个数,即串的长度
strCompare(S,T):若S>T,返回值>0,若S=T,返回0,若S
操作index的实现算法
//T为非空串。若主串S中第pos个字符之后存在与T相等的子串
//则返回第一个这样的子串在S中的位置,否则返回0
func index(S string, T string, pos int) int {
var n,m,i int
var sub string
if (pos > 0) {
n = strLength(S) //得到主串S的长度
m = strLength(T) //得到子串T的长度
i = pos
for (i <= n-m+1) {
subString(sub, S, i, m) //取主串第i个位置长度与T相等子串给sub
if (strCompare(sub,T) != 0) {//如果两串不相等
i+1
} else {//如果两串相等
return i //则返回i值
}
}
}
return 0 //若无子串与T相等,返回0
}
当中用到了strLength、subString、strCompare等基本操作来实现。
5.5 串的存储结构
5.5.1 串的顺序存储结构
5.5.2 串的链式存储结构
5.6 朴素的模式匹配算法
子串的定位操作通常称为串的模式匹配
假设我们要从下面的主串S="goodgoogle"中,找到T="google"这个子串的位置。我们通常需要下面的步骤。
简单的说,就是对主串的每一个字符作为子串开头,与要匹配的字符串进行匹配。对主串做大循环,每个字符开头做T得长度的小循环,直到匹配成功或全部遍历完成为止。
前面我们已经用串的其他操作实现了模式匹配的算法index。现在考虑不用串的其他操作,而是只用基本的数组来实现同样的算法。注意我们假设主串S和要匹配的子串T的长度存在S[0]与T[0]中。实现代码如下:
//返回子串T在主串S中第pos个字符之后的位置,若不存在,则函数返回值为0
//T非空,1<=pos<=strLength(S)
func index(S string, T string, pos int) int {
i := pos //i用于主串S中当前位置下标,若pos不为1,则从pos位置开始匹配
j := 1 //j用于子串T中当前位置下标值
for (i <= S[0] && j <= T[0]) {//若i小于S长度且j小于T的长度时循环
if (S[i] == T[j]) {//两字符相等则继续
i + 1
j + 1
} else {//指针后退重新开始匹配
i = i-j+2 //退回到上次匹配首位的下一位
j = 1 //j退回到子串T的首位
}
}
if (j > T[0]) {
return i - T[0]
} else {
return 0
}
}
5.7 KMP模式匹配算法
从这个算法的研究角度来理解为什么它比朴素算法要好。
我们在朴素的模式匹配算法中,主串的i值是不断地回溯来完成的。而我们的分析发现,这种回溯其实是可以不需要的--我们的KMP模式匹配算法就是为了让这没必要的回溯不发生。
既然i值不回溯,也就是不可以变小,那么要考虑的变化就是j值了。通过观察也可发现,我们屡屡提到了T串的首字符与自身后面字符的比较,发现如果有相等字符,j值的变化就会不相同。也就是说,这个j值的变化与主串其实没什么关系,关键就取决于T串的结构中是否有重复的问题。
j值的多少取决于当前字符之前的串的前后缀的相似度。
我们把T串各个位置的j值的变化定义为一个数组next,那么next的长度就是T串的长度。
我们可以根据经验得到如果前后缀一个字符相等,k值是2,两个字符k值是3,n个相等k值就是n+1。
5.7.3 KMP模式匹配算法实现
//通过计算返回子串T的next数组
func get_next(T string, *next int) {
i := 1
j := 0
next[1] = 0
for (i < T[0]) {//此处T[0]表示串T的长度
if(j == 0 || T[i] == T[j]) {//T[i]表示后缀的单个字符
//T[j]表示前缀的单个字符
i+1
j+1
next[i] = j
} else {
j = next[j] //若字符不相同,则j值回溯
}
}
}
这段代码的目的就是为了计算出当前要匹配的串T的next数组。
//返回子串T在主串S中第pos个字符之后的位置。若不存在,则函数返回值为0。
//T非空,1<=pos<=strLength(S)
func index_KMP(S string, T string, pos int) int {
i := pos //i用于主串S当前位置下标值,若pos不为1,则从pos位置开始匹配
j := 1 //j用于子串T中当前位置下标值
var next []int //定义一next数组
get_next(T, next) //对串T作分析,得到next数组
for (i <= S[0] && j <= T[0]) {//若i小于S的长度且j小于T的长度时,循环继续
if (j == 0 || S[i] == T[j]) {//两字母相等则继续,与朴素算法增加了j=0判断
i+1
j+1
} else {//指针后退重新开始匹配
j = next[j] //j退回合适的位置,i值不变
}
}
if (j > T[0]) {
return i - T[0]
} else {
return 0
}
}
加粗的为相对于朴素匹配算法增加的代码,改动不算大,关键就是去掉了i值回溯的部分。对于get_next函数来说,若T的长度为m,因只涉及到简单的单循环,其时间复杂度为O(m),而由于i值的不回溯,使得index_KMP算法效率得到了提高,for循环的时间复杂度为O(n)。因此,整个算法的时间复杂度为O(n+m)。相较于朴素模式匹配算法的O((n-m+1)*m)来说,是要好一些。
这里也需要强调,KMP算法仅当模式与主串之间存在许多“部分匹配”的情况下才体现出它的优势,否则两者差异并不明显。
5.7.4 KMP模式匹配算法改进
假设取代的数组为nextval,增加了加粗部分,代码如下:
//求模式串T的next函数修正值并存入数组nextval
func get_nextval(T string, nextval int) {
i := 1
j := 0
nextval[1] = 0
for (i
实际匹配算法,只需要将“get_next(T, next)" 改为”get_nextval(T,next)“即可,这里不再重复。
5.7.5 nextval数组值推导
先算出next数组的值,然后再分别判断。
总结改进过的KMP算法,它是在计算出next值的同时,如果a位字符与它next值指向的b位字符相等,则该a位的nextval就指向b位的nextval值,如果不等,则该a位的nextval值就是它自己a位的next的值。
5.8 总结回顾
第6章 树
树是n(n>=0)个结点的有限集。n=0时称为空树。在任意一颗非空树中:(1)有且仅有一个特定的称为根(root)的结点;(2)当n>1时,其余结点可分m(m>0)个互不相交的有限集T1、T2、......、Tm,其中每一个集合本身又是一颗树,并且称为根的子树。
6.2 树的定义
一对多的数据结构--”树“
对于树的定义还需要强调两点:
1.n>0时根节点是唯一的,不可能存在多个根节点。
2.m>0时,子树的个数没有限制,但它们一定是互不相交的。
6.2.1 结点分类
树的度是树内各结点的度的最大值。图例这课树结点的度的最大值是结点D的度,为3,所以树的度也为3。
6.2.2 结点间关系
6.2.3 树的其他相关该概念
树中结点的最大层次称为树的深度或高度,当前树的深度为4。
对比线性表与树的结构
线性结构 第一个数据元素:无前驱;最后一个数据元素:无后继;中间元素:一个前驱一个后继。
树结构 根结点:无双亲,唯一;叶子结点:无孩子,可以多个;中间结点:一个双亲多个孩子
6.3 树的抽象数据类型
树
data 树是由一个根结点和若干颗子树构成。树中结点具有相同数据类型和层次关系。
operation
initTree(*T): 构造空树T。
destroyTree(*T): 销毁树T。
createTree(*T, definition): 按definition中给出树的定义来构造树。
clearTree(*T): 若树T存在,则将树T清为空树。
treeEmpty(T): 若T为空树,返回true,否则返回false。
treeDepth(T): 返回T的深度。
root(T): 返回T的根结点。
value(T,cur_e): cur_e是树T中一个结点,返回此结点的值。
assign(T,cur_e,value):给树T的结点cur_e赋值为value。
parent(T,cur_e): 若cur_e是树T的非根结点,则返回它的双亲,否则返回空。
leftChild(T,cur_e):若cur_e是树T的非叶结点,则返回它的最左孩子,否则返回空。
right_Sibling(T,cur_e):若cur_e有右兄弟,则返回它的右兄弟,否则返回空。
insertChild(*T, *p,i, c):其中p指向树T的某个结点,i为所指结点p的度加上1,非空树c与T不相交,操作结果为插入c为树T中p指结点的第i颗子树。
deleteChild(*T, *p, i): 其中p指向树T的某个结点,i为所指结点p的度,操作结果为删除T中p所指结点的第i颗子树。
充分利用顺序存储和链式存储结构的特点,完全可以实现对树的存储结构的表示。我们这里要介绍三种不同的表示法:双亲表示法、孩子表示法、孩子兄弟表示法。
6.4.1 双亲表示法
以下是我们的双亲表示法的结点结构定义代码。
//树的双亲表示法结点结构定义
var max_tree_size int = 100
var TElemType int // 树结点的数据类型,目前暂定为整型
type PTNode struct {//结点结构
data TElemType //结点数据
parent int //双亲位置
}
type PTree struct { //树结构
nodes[max_tree_size] PTNode //结点数组
r,n int //根的位置和结点数
}
6.4.2 孩子表示法
以下是我们的孩子表示法的结构定义代码
// 树的孩子表示法结构定义
var MAX_TREE_SIZE 100
// 孩子结点
type CTNode struct {
child int
Next *CTNode
}
// 表头结构
type CTBox struct {
data TElemType
firstchild ChildPtr
}
// 树结构
type CTree struct {
nodes[MAX_TREE_SIZE] CTBox //结点数组
r, n int //根的位置和结点数
}
6.4.3 孩子兄弟表示法
结构定义代码如下。
// 树的孩子兄弟表示法结构定义
type CSNode struct {
data TElemType
firstchild, rightsib *CSNode
}
6.5 二叉树的定义
6.5.1 二叉树特点
6.5.2 特殊二叉树
1.斜树
2.满二叉树
3.完全二叉树
6.6 二叉树的性质
6.6.1 二叉树性质1
在二叉树的第i层上至多有2^(i-1)个结点(i>=1)
6.6.2 二叉树性质2
深度为k的二叉树至多有2^k - 1个结点(k >= 1)
6.6.3 二叉树性质3
对任何一颗二叉树T,如果其终点结点数为n0,度为2的结点数为n2,则n0=n2+1
6.6.4 二叉树性质4
具有n个结点的完全二叉树的深度为(log2 n) +1 ((x)表示不大于x的最大整数)
6.6.5 二叉树性质5
如果对一颗有n个结点的完全二叉树(其深度为(log2 n) +1)的结点按层序编号(从第1层到第(log2 n) +1层,每层从左到右),对任一结点i(1=< i = 有:1.如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲是结点(i/2) 2.如果2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩子是结点2i 3.如果2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1。 6.7 二叉树的存储结构 6.7.1 二叉树顺序存储结构 6.7.2 二叉链表 以下是我们的二叉链表的结点结构定义代码 6.8 遍历二叉树 6.8.1 二叉树遍历原理 二叉树的遍历是指从根结点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次。 这里有两个关键词:访问和次序。 6.8.1 二叉树遍历方法 1.前序遍历 规则是若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树。 2.中序遍历 规则是若树为空,则空操作返回,否则从根结点开始(注意并不是先访问根结点),中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树。 3.后序遍历 规则是若树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根结点。 4.层序遍历 规则是若树为空,则空操作返回,否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。 6.8.3 前序遍历算法 6.8.4 中序遍历算法 6.8.5 后序遍历算法 二叉树遍历的性质 已知前序遍历序列和中序遍历序列,可以唯一确定一颗二叉树。 已知后序遍历序列和中序遍历序列,可以唯一确定一颗二叉树。 6.9 二叉树的建立 由标明空子树的先跟遍历序列建立一颗二叉树的操作算法 由完全二叉树的顺序存储结构建立其二叉链式存储结构 6.10 线索二叉树 6.10.1 线索二叉树原理 6.10.2 线索二叉树结构实现 6.11 树、森林与二叉树的转换 6.11.1 树转换为二叉树 6.11.2 森林转换为二叉树 6.11.3 二叉树转换为树 6.11.4 二叉树转换为森林 6.11.5 树与森林的遍历 6.12 赫夫曼树及其应用 6.12.1 赫夫曼树 6.12.2 赫夫曼树定义与原理 带权路径长度WPL最小的二叉树称做赫夫曼树,也称为最优二叉树 构造赫夫曼树的赫夫曼算法描述: 1.根据给定的n个权值{w1,w2,...,wn}构成n棵二叉树的集合F={T1,T2,...,Tn}, 2.在F中选取两颗根结点的权值最小的树作为左右子树构造一棵新的二叉树,且置新的二叉树的根结点的权值为其左右子树根结点的权值之和。 3.在F中删除这两颗树,同时将新得到的二叉树加入F中。 4.重复2和3步骤,直到F只含一颗树为止。这颗树便是赫夫曼树。 6.12.3 赫夫曼编码 6.13 总结回顾 第7章 图 7.2 图的定义 图是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。 7.2.1 各种图定义 7.2.2图的顶点与边间关系 7.2.3 连通图相关术语 7.2.4 图的定义与术语总结 7.3 图的抽象数据类型 7.4 图的存储结构 7.4.1 邻接矩阵 // 图的邻接矩阵存储的结构 有了这个结构定义,我们构造一个图,其实就是给顶点表和边表输入数据的过程。 // 建立无向网图的邻接矩阵表示 7.4.2 邻接表 7.4.3 十字链表 7.4.4 邻接多重表 7.4.5 边集数组 7.5 图的遍历 图的遍历和树的遍历类似,我们希望从图中某一顶点出发访遍图中其余顶点,且使每一个顶点仅被访问一次,这一过程就叫做图的遍历。 7.5.1 深度优先遍历 深度优先遍历其实是一个递归的过程,还有就像是一棵树的前序遍历。它从图中某个顶点v出发,访问此顶点,然后从v的未被访问的邻接点出发深度优先遍历图,直至图中所有和v有路径相通的顶点都被访问到。 // 邻接矩阵的深度优先递归算法 // 邻接矩阵的深度遍历操作 // 邻接表的深度优先递归算法 // 邻接表的深度遍历操作 7.5.2 广度优先遍历 图的广度优先遍历类似于树的层次遍历 // 邻接矩阵的广度优先递归算法 // 邻接矩阵的广度遍历操作 // 邻接表的广度优先递归算法 // 邻接表的广度遍历操作 7.6 最小生成树 找连通网的最小生成树,经典的有两种算法,普里姆算法和克鲁斯卡尔算法。 7.6.1 普里姆(Prim)算法 // Prim算法生成最小生成树 7.6.2 克鲁斯卡尔( Kruskal )算法 // Kruskal算法生产最小生成树 7.7 最短路径 对于网图来说,最短路径,是指两顶点之间经过的边上权值之和最少的路径,并且我们称路径上的第一个顶点是源点,最后一个顶点是终点。 7.7.1 迪杰斯特拉(Dijkstra)算法 基于已经求出的最短路径的基础上,求得更远顶点的最短路径,最终得到你要的结果。 7.7.2 弗洛伊德(Floyd)算法 // Floyd算法,求网图G中各顶点v到其余顶点w最短路径P[v][w]及带权长度D[v][w] 求最短路径的显示代码可以这样写: //获得第一个路径顶点线标 // 打印源点 // 如果路径顶点下表不是终点 // 打印路径顶点 // 获得下一个路径顶点下标 //打印终点 7.8 拓扑排序 7.8.1 拓扑排序介绍 7.8.2 拓扑排序算法 在拓扑排序算法中,涉及的结构代码如下: // 边表结点 // 邻接点域,存储该顶点对应的下标 // 用于存储权值,对于非图网可以不需要 // 链域,指向下一个邻接点 // 顶点表结点 // 顶点入度 // 顶点域,存储顶点信息 // 边表头指针 // 图中当前顶点数和边数 在算法中,我还需要辅助的数据结构一栈,用来存储处理过程中入度为0的顶点,目的是为了避免每个查找时都要去遍历顶点表找有没有入度为0的顶点。 // 拓扑排序,若GL无回路,则输出拓扑排序序列并返回OK,若有回路返回ERROR // 用于栈指针下标 // 用于统计输出顶点的个数 // 建栈存储入度为0的顶点 // 将入度为0的顶点入栈 // 出栈 // 打印此顶点 // 统计输出顶点数 // 对此顶点弧表遍历 // 将k号顶点邻接点的入度减1 // 若为0则入栈,以便于下次循环输出 // 如果count小于顶点数,说明存在环 7.9 关键路径 7.9.1 关键路径算法原理 7.9.2 关键路径算法 7.10 总结回顾 图的应用是我们这 章浓墨重彩的 部分 共谈了 种应用 最小生成树、最 短路径和有向无环图的应用. 最小生成树,我们讲了两种算法:普里姆 Prim 算法和克鲁斯卡尔( ka1 算法。普里姆算法像是走一步看一步的思维方式,逐步生成最小生成树。而克鲁斯卡 尔算法则更有全局意识,直接从圈中最短权值的边入手,找寻最后的答案。 最短路径的现实应用非常多,我们也介绍了两种算法。迪杰斯特拉 Dijkstra 法更强调单源顶点查找路径的方式,比较符合我们正常的思路,容易理解原理,但算 法代码相对复杂。而弗洛伊德 Fbyd 算法则完全抛开 单点的局限思维方式,巧妙 地应用矩阵的变换,用最清爽的代码实现 多顶点间最短 程求解的方案, 理理解 有难度,但算法编写很简洁。 有向无环圈时常应用于工程规划中,对于整个工程或系统来说,我们 方面关心 的是工程能否顺利进行的问题,通过拓 排序的方式,我们可以有效地分析出 个有 向图是否存在环,如果不存在,那色的拓扑序列是什么?另 方面关心的是整个工程 完成所必须的最短时间问题,利用求关键路径的算法,可以得到最短完成工程的工期 以及关键的活动有哪些。 第8章 查找 8.3.1 顺序表查找算法 8.3.2 顺序表查找优化 8.4 有序表查找 8.4.1 折半查找 二分查找 前提是线性表中的记录 必须是关键码有序(通常从小到大有序) ,线性表必须采用顺序存储。折半查找的基 本思想是:在有序表中,取中间记录作为比较对象,若给定值与中间记录的关键字相 等,则查找成功;若给定值小于中间记录的关键字,则在中间记录的左半区继续查找; 若给定值大于中间记录的关键字,则在中间记录的右半区继续查找。不断重复上述过程,直到查找成功,或所有查找区域无记录,查找失败为止。 因此最终我们折半算法的时间复杂度为 0(log n) ,它显然远远好于顺序查找的 0(n) 时间复杂度了 8.4.2 插值查找 mid=low+ (high-low) * (key-a [low]) / (a[high]-a[low] ) ; /*插值*/ 插值查找(Interpolation Search)是 根据要查找的关键字 key 与查找表中最大最小记录的关键字比较后的查找方法,其核心 就在于插值的计算公式(key-a [low]) / (a[high]-a[low] ) 8.4.3 斐波那契查找 斐波那契查找算法的核心在于: 1) 当key=a[mid] 时,查找就成功;// 二叉树的二叉链表的结点结构定义
type BiTNode struct {// 结点结构
data TElemType // 结点数据
lchild,rchild *BiTNode // 左右孩子指针
}
// 二叉树的前序遍历递归算法
func PreOrderTraverse (T Bitree) {
if T == nil
return
fmt.Printf("%d",T.data) //显示节点数据,可以更改为其他对节点操作
PreOrderTraverse(T.lchild) //再先遍历左子树
PreOrderTraverse(T.rchild) // 最后先序遍历右子树
}
// 二叉树的中序遍历递归算法
func InOrderTraverse (T Bitree) {
if T == nil
return
InOrderTraverse(T.lchild) //中序遍历左子树
fmt.Printf("%d",T.data) //显示结点数据他,可以更改为其他对结点操作
InOrderTraverse(T.rchild) // 最后中序遍历右子树
}
// 二叉树的后序遍历递归算法
func PostOrderTraverse(T Bitree) {
if T == nil
return
PostOrderTraverse(T.lchild) // 先后序遍历左子树
PostOrderTraverse(T.rchild) // 再后序遍历右子树
fmt.Printf("%d",T.data) //显示结点数据,可以更改为其他对结点操作
}
func HaveEmptyCreate(arr []int) *TreeNode {
i = i + 1
if i >= len(arr) {
return nil
}
var t TreeNode
if arr[i] != 0 {
t = TreeNode{nil,arr[i],nil}
t.Left = HaveEmptyCreate(arr)
t.Right = HaveEmptyCreate(arr)
} else {
return nil
}
return &t
}
func TreeCreate(i int,arr []int) *TreeNode {
t := &TreeNode{nil,arr[i],nil}
if i
// 对边集数组Edge结构的定义
type Edge struct {
begin int
end int
weight int
}
// 顺序查找,a为数组,n为要查找的数组个数,key为要查找的关键字
func Sequential_Search(a []int, n int, key int) int {
var i int
for i = 1; i <= n; i++ {
if a[i] == key
return i
}
return 0
}
// 有哨兵顺序查找
func Sequential_Search2(a []int, n int, key int) int {
var i int
a[0] = key // 设置a[0]为关键字值,我们称之为“哨兵”
i = n // 循环从数组尾部开始
while(a[i] != key) {
i --
}
return i // 返回0则说明查找失败
}
// 折半查找
func Binary_Search(a []int, n int, key int) {
var low,high,mid int
low = 1 // 定义最低下标为记录首位
high = n // 定义最高下标为记录首位
while(low <= high) {
mid = (low +high)/2 // 折半
if (keya[mid]) // 若查找值比中值大
low = mid + 1 // 最低下标调整到中位下标大一位
else
return mid // 若相等则说明mid即为查找到的位置
}
return 0
}
// 斐波那契查找
func Fibonacci_Search(a []int, n int, key int) {
var low,high,mid,i,k int
low = 1 // 定义最低下标为记录首位
high = n // 定义最高下标为记录末位
k = 0
while(n > F[k] -1) //计算n位于斐波那契数列的位置
k++
for i = n; i