数据结构与算法
程序=数据结构+算法。
因此我认为自己该再好好补充下数据结构相关的知识,今天开始就以著名老师严蔚敏的数据结构一书为参考,边学边记。
线性表:
线性表基本API | 初始条件 | 操作结果 |
InitList(&L) | 构造一个空的线性表L。 | |
DestroyList(&L) | 线性表L已存在。 | 销毁线性表L。 |
ClearList(&L) | 线性表L已存在。 | 将L重置为空表。 |
ListEmpty(L) | 线性表L已存在。 | 若L为空表,返回true,否则返回false。 |
ListLength(L) | 线性表L已存在。 | 返回L中元素的个数。 |
GetElem(L, i, &e) | 线性表L已存在,1<=i<=ListLength(L)。 | 用e返回L中第i个元素的值。 |
LocalElem(L, e, compare()) | 线性表L已存在,compare()用来对数据元素的判定。 | 返回L中第一个与e满足条件compare()元素的位序,若都不满足,返回0。 |
PriorElem(L, cur_e, &pre_e) | 线性表L已存在。 | 用pre_e返回cur_e的前驱。 |
NextElem(L, cur_e, &next_e) | 线性表L已存在。 | 用next_e返回cur_e的后继。 |
ListInsert(&L, i, e) | 线性表L已存在,1<=i<=ListLength(L)+1。 | 在L中在第i个元素前插入元素e |
ListDelete(&L, i, &e) | 线性表L已存在且非空,1<=i<=ListLength(L)。 | 删除第i个元素,并用e返回其值。 |
ListTraverse(L, visit()) | 线性表L已存在。 | 依此对L的每个数据调用visit(),一旦visit()调用失败,则操作失败。 |
顺序表:逻辑位置和内存物理位置相匹配,所以可以取任一位置元素时,时间复杂度为O(1);但是在插入或删除操作时,时间复杂度为O(N)。
俩个线性表合并:
不排序时间复杂度:O(length(n1)*length(n2))
排序后时间复杂度:O(nlog2n)+0(length(n1)+length(n2)),其中nlog2n是快速排序的时间复杂度
所以,若以线性表表示集合并进行集合的各种运算,应先对表中元素进行排序。
单链表:逻辑位置和物理位置无关,每个节点存后一个节点的位置信息,所以在插入或删除操作时,若知道节点的物理位置,则时间复杂度为O(1),如果按位序操作,复杂度为O(n),因为要从头开始寻找;但是读取任一位置元素时,时间复杂度为O(n)。
栈:先进后出,只有一个口来进栈出栈。
队列:先进先出,一端进,一端出。
栈的一些应用:相比较数组,有些问题用栈比较合适,因为不用考虑数组下标的问题,突出问题的重点,譬如仅仅是删除和添加节点同一端节点的问题。
串:即字符串。在子串的匹配算法中,有一种优化的算法,不需回溯主串的指针,不过一般情况下普通匹配算法也有O(m+n)的效率,所以用的地方不多,除非主串中有很多连续字符和子串的部分连续字符相等。用普通匹配算法最坏的效率是O(m*n)。
特殊矩阵:如aij=aji(1<=i,j,<=n)这样的特殊矩阵,可用一维数组b[k]来存取矩阵的值,可将n2个元压缩存储到n(n+1)/2个元的空间中。其中k=i*(i-1)/2+j-1(i>=j)或k=j*(j-1)/2+i-1(i<j)D,行序为主序。
稀疏矩阵:在m*n的矩阵中,有t个非零元素,令a=t/(m*n),a称为稀疏因子,通常认为a<=0.05时为稀疏矩阵。
稀疏矩阵可由非零元的三元组及其行列数唯一确定。
广义表:LS=(a1,a2,···,an) 其中,a1是表头,(a2,···,an)是表尾,ai(1<=i<=n) 可以是单个元素也可以是广义表。譬如A=(e),B=(a,(g,h)),C=(A,B) 那么C=(e,a,(g,h))。
二叉树:
性质1:在二叉树的第i层上最多有2i-1个节点(i>=1)
性质2:深度为k的二叉树至多有2k-1个节点(k>=1)
性质3:对任何一颗二叉树T,若其终节点数为n0,度数为2的节点数为n2,则n0=n2+1。
满二叉树:所有非终节点都有俩个子节点的二叉树称为满二叉树。
完全二叉树:深度为k,有n个节点的二叉树,当且仅当其每个节点都与深度为k的满二叉树中编号1至n的节点一一对应时,称为满二叉树。
性质4(完全二叉树):具有n个节点的完全二叉树的深度为[log2n]+1。(这里[]表示不大于log2n的最大整数)
性质5(完全二叉树):如果对一颗有n个节点的完全二叉树的节点按层序编号(上到下,左到右),则对任意节点i(1<=i<=n),有
(1)如果i=1,则节点i是数的根,无双亲;如果i>1,则其双亲parent(i)是节点[i/2]。 (这里[]表示不大于log2n的最大整数)
(2)如果2i>n,则节点i无左孩子(i为叶子节点);否则左孩子lchild(i)是节点2i。
(3)如果2i+1>n,则节点i无右孩子;否则右孩子rchild(i)是节点2i+1。
二叉树的存储结构:
1.顺序存储结构:用一个一维数组,自上而下,自左而右的存储树的元素,当为空时,元素值为0。
2.链式存储结构:分为二叉链表和三叉链表,二叉链表中包含 数值域和左、右指针域;三叉链表比二叉链表多了个双亲指针域。
二叉树的存储和遍历:
var tree_values = ["A","B","C",null,null,"D","E",null,"G",null,null,"F",null,null,null]; //null表示此节点为空 var tree_values_index = 0; //按先序次序画二叉树 function createBiTree(tree){ if (!(tree.t_data = tree_values[tree_values_index++])){ } //给树的data域赋值,若是null,则表明此为空节点,结束。否则,继续递归。 else{ tree.lchild={}; tree.rchild={}; createBiTree(tree.lchild); createBiTree(tree.rchild); } } var biTree = {}; createBiTree(biTree); //先序遍历二叉树 function preOrderTraverseTree(tree){ if (tree.t_data){ cc.log(tree.t_data); preOrderTraverseTree(tree.lchild); preOrderTraverseTree(tree.rchild); } } preOrderTraverseTree(biTree); //A B C D E G F
线索二叉树:若结点有左子树,则lchild域为左孩子,否则为前驱;若结点有右子树,则rchild域为右孩子,否则为后继。其中指向前驱和后继的指针,叫做线索。加上线索的二叉树叫做线索二叉树。对二叉树以某种次序遍历使其变为线索二叉树的过程叫做线索化。
折半查找法:对于顺序有序表,可用折半查找发,效率为O(log2n)。以下为算法代码。
var orderedNumbers = [1,3,4,6,7,9,12,14,16,36]; function binarySearch(array,key){ var low = 0; var high = array.length-1; var mid; while(low<=high){ mid = parseInt((low+high)/2); if (array[mid]==key) return mid; else if (array[mid]<key) low = mid + 1; else high = mid - 1; } return "没找到"; } cc.log("key在数组位序"+binarySearch(orderedNumbers,12)+"的地方");
二叉排序树:
又名:二叉搜身树,BST树。BINARY_SEARCH_TREE
定义:左子树上的所有节点的值均小于根节点的值,右子树上的所有节点值均大于根节点的值,左、右子树分别为二叉排序数。
图示:
二叉排序树节点的添加、查找和删除代码:
function searchBST(currentNode,key,node,finalNode){ //寻找要添加的节点,为下面的traverseBST服务 if (!currentNode.t_data){ //如果找不到,则添加对应值的节点 finalNode.t_data=node.t_data; finalNode.lchild=node.lchild; finalNode.rchild=node.rchild; return false; } else if (currentNode.t_data==key){ //找到节点的话,不进行添加。 cc.log("找到"+currentNode.t_data); return true; } else if (currentNode.t_data>key){ //如果节点的值大于key值,则递归检查节点的左子树的值 if(!currentNode.lchild) currentNode.lchild={}; return searchBST(currentNode.lchild,key,currentNode,finalNode); } else{ //如果节点的值小于key值,则递归检查节点的右子树的值 if(!currentNode.rchild) currentNode.rchild={}; return searchBST(currentNode.rchild,key,currentNode,finalNode); } } function traverseBST(tree,f_key){ //寻找要添加的节点 var f_finalNode={}; if(!searchBST(tree,f_key,{},f_finalNode)){ //这里的f_finalNode和tree节点的指针指向了同一内存地址。 if (!f_finalNode.t_data) tree.t_data=f_key; //如果是空树 else if (f_finalNode.t_data>f_key) f_finalNode.lchild.t_data=f_key; //如果节点值大于key,则在节点的左子树中添加对应的key值 else f_finalNode.rchild.t_data=f_key; //如果节点值小于key,则在节点的右子树中添加对应的key值 } } function theFinalRchild(node,tmp){ //寻找指定节点的右叶子节点 if(!node.rchild) node.rchild={}; if(node.rchild.t_data) theFinalRchild(node.rchild,tmp); //如果当前节点的右节点有值,则继续递归检查 else{ //否则,进行赋值操作 node.rchild.t_data=tmp.t_data; node.rchild.lchild=tmp.lchild; node.rchild.rchild=tmp.rchild; } } function deleteNode(theNode){ //按照一定规则删除节点,并使其他节点按二叉排序树的规则排列 if(((theNode.lchild&&!theNode.lchild.t_data)&&(theNode.rchild&&!theNode.rchild.t_data)) || ((theNode.lchild&&!theNode.lchild.t_data)&&!theNode.rchild) || ((theNode.rchild&&!theNode.rchild.t_data)&&!theNode.lchild) || (!theNode.lchild && !theNode.rchild)) theNode.t_data = null; //当节点有左或右子节点的但是其值皆不存在时;或者没有左右子树时,直接删除节点值 else if(!theNode.lchild){ //当节点只有右子树时 theNode.t_data = theNode.rchild.t_data; if(!theNode.rchild.rchild) theNode.rchild.rchild = {}; theNode.rchild = theNode.rchild.rchild; if(!theNode.rchild.lchild) theNode.rchild.lchild = {}; theNode.lchild = theNode.rchild.lchild; }else if(!theNode.rchild){ //当节点只有左子树时 theNode.t_data = theNode.lchild.t_data; if(!theNode.lchild.rchild) theNode.lchild.rchild = {}; theNode.rchild = theNode.lchild.rchild; if(!theNode.lchild.lchild) theNode.lchild.lchild = {}; theNode.lchild = theNode.lchild.lchild; }else{ //当节点有左、右子树时。删除原理:先让被删节点的左节点上位,然后再把被删节点的右节点添加到上位后节点的右叶子的右节点上。 var finalChild = theNode.rchild; //存储被删节点的右节点 var rChild = theNode.lchild.rchild; //存储被删节点的左节点的右节点 //因为以下赋值操作会破坏原有的节点间结构,所以先把会破坏的信息进行存储,如上。 theNode.t_data = theNode.lchild.t_data; theNode.lchild = theNode.lchild.lchild; theNode.rchild = rChild; theFinalRchild(theNode,finalChild); //把之前存储的右节点赋值给当前节点的右叶子的右节点。 } } function deleteBST(tree,key){ //寻找要删除的节点 if(!tree.t_data) cc.log("树中没找到"+key+"对应的节点"); else{ if(tree.t_data == key) deleteNode(tree); //如果找到对应的key节点,则删之。 else if(tree.t_data>key){ //如果节点的值大于key值,则递归检查节点的左子树的值 if(!tree.lchild) tree.lchild = {}; deleteBST(tree.lchild,key); } else{ //如果节点的值小于key值,则递归检查节点的右子树的值 if(!tree.rchild) tree.rchild = {}; deleteBST(tree.rchild,key); } } } var theBST={}; //二叉排序树 var theBST_values = [45,24,53,45,12,24,90]; //当第二次遇到45和24时,会打印出“找到45”,“找到24”。 for(var i=0; i<theBST_values.length; i++) traverseBST(theBST,theBST_values[i]); //给二叉排序树加值,如果值已存在,则输出找到。 cc.log(theBST.rchild.t_data); //打印:53 deleteBST(theBST,23); //打印:树中没找到23对应的节点 deleteBST(theBST,53); //删除53对应的节点 cc.log(theBST.rchild.t_data); //打印:90
平衡二叉树:
又名:balanced binary tree,AVL树 。AVL是发明这棵树的人。
定义:左子树和右子树的深度差的绝对值不超过1,并且左子树和右子树也都是平衡二叉树。
图示:
哈希表:在顺序查找和折半查找(或二叉排序树)时,一般都需要比较,譬如前者比较结果为“=”或“!=”俩种可能,后者为“=”,“<”,“>”三种可能。理想的状态是不需要比较,一次就能找到相应的记录。这时就需要用到哈希表。譬如在a=[1,3,6,7,13,24,33]这个数组中,想找这个数组里的x,可以用f(x)来确定x在数组a中的位置,譬如f(6)=2,则当要找6这个纪录时,可用a[f(6)]来确定纪录是否存在并且等于6。这里的f(x)既是哈希函数。但是哈希表有个问题就是容易冲突,譬如函数设置不当的话,f(6)和f(7)可能都等于2,这时就要设计一个好的f(x)哈希函数来使其趋向完美。