数据结构与算法

程序=数据结构+算法。

因此我认为自己该再好好补充下数据结构相关的知识,今天开始就以著名老师严蔚敏的数据结构一书为参考,边学边记。

线性表:

线性表基本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)哈希函数来使其趋向完美。

posted @ 2014-06-15 22:02  奋斗中的小鸟  阅读(423)  评论(0编辑  收藏  举报