算法&&数据结构

285 阅读5分钟

递归算法

汉诺塔

汉诺塔问题是指有三根杆子A,B,C。C杆上有若干碟子,把所有碟子从A杆上移到C杆上,每次只能移动一个碟子,大的碟子不能叠在小的碟子上面。求最少要移动多少次?

解决思路:解决n块盘子从A移动到C,那么我只需要先把n-1块盘子从A移到B,然后把最下面的第n块盘子从A移到C,最后把n-1块盘子从B移到C(这就完成了)。

    let i = 0
    // 汉诺塔
    function move(n, start, end) {
        console.log(`第${++i}步: 将${n}号盘子${start} ------> ${end}`)
    }

    function hanio(n, start, pos, end) {
        if (n === 1) {
            move(n, start, end)
        } else {
            hanio(n - 1, start, end, pos)
            move(n, start, end)
            hanio(n - 1, pos, start, end)
        }
    }
    hanio(5, 'A', 'B', 'C')

定义

树(tree)是n(n>=0)个结点的有限集。n=0时称为空树。在任意一颗非空树中:

  • 有且仅有一个特定的称为根(Root)的结点;
  • 当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1、T2、......、Tn,其中每一个集合本身又是一棵树,并且称为根的子树。

树的定义还需要强调以下两点:
1、n>0时根结点是唯一的,不可能存在多个根结点,数据结构中的树只能有一个根结点。
2、m>0时,子树的个数没有限制,但它们一定是互不相交的。

结点的度

结点拥有的子树数目称为结点的度。

结点层次

从根开始定义起,根为第一层,根的孩子为第二层,以此类推。

树的深度

树中结点的最大层次数称为树的深度或高度。

二叉树

定义

二叉树是n(n>=0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树组成。

二叉树的特点

1、每个结点最多有两颗子树,所以二叉树中不存在度大于2的结点。
2、左子树和右子树是有顺序的,次序不能任意颠倒。
3、即使树中某结点只有一棵子树,也要区分它是左子树还是右子树。

二叉树的特性

1、在二叉树的第i层上最多有2(i-1) 个节点 。(i>=1)
2、二叉树中如果深度为k,那么最多有2k-1个节点。(k>=1)
3、n0=n2+1 n0表示度数为0的节点数,n2表示度数为2的节点数。
4、在完全二叉树中,具有n个节点的完全二叉树的深度为[log2n]+1,其中[log2n]是向下取整。

二叉树的遍历

二叉树的遍历是指从二叉树的根结点出发,按照某种次序依次访问二叉树中的所有结点,使得每个结点被访问一次,且仅被访问一次。 二叉树的访问次序可以分为四种:

  • 前序遍历

前序遍历通俗的说就是从二叉树的根结点出发,当第一次到达结点时就输出结点数据,按照先向左在向右的方向访问。

/*二叉树的前序遍历递归算法*/
void PreOrderTraverse(BiTree T)
{
    if(T==NULL)
    return;
    printf("%c", T->data);  /*显示结点数据,可以更改为其他对结点操作*/
    PreOrderTraverse(T->lchild);    /*再先序遍历左子树*/
    PreOrderTraverse(T->rchild);    /*最后先序遍历右子树*/
}
  • 中序遍历

中序遍历就是从二叉树的根结点出发,当第二次到达结点时就输出结点数据,按照先向左在向右的方向访问。

/*二叉树的中序遍历递归算法*/
void InOrderTraverse(BiTree T)
{
    if(T==NULL)
    return;
    InOrderTraverse(T->lchild); /*中序遍历左子树*/
    printf("%c", T->data);  /*显示结点数据,可以更改为其他对结点操作*/
    InOrderTraverse(T->rchild); /*最后中序遍历右子树*/
}
  • 后序遍历
/*二叉树的后序遍历递归算法*/
void PostOrderTraverse(BiTree T)
{
    if(T==NULL)
    return;
    PostOrderTraverse(T->lchild);   /*先后序遍历左子树*/
    PostOrderTraverse(T->rchild);   /*再后续遍历右子树*/
    printf("%c", T->data);  /*显示结点数据,可以更改为其他对结点操作*/
}
  • 层序遍历

层次遍历就是按照树的层次自上而下的遍历二叉树。

排序算法

快速排序

快速排序(Quick Sort)使用分治法策略
它的基本思想是:选择一个基准数,通过一趟排序将要排序的数据分割成独立的两部分;其中一部分的所有数据都比另外一部分的所有数据都要小。然后,再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

	function quickSort(arr, l, h) {
            if (l < h) {
                let i = l
                let j = h
                let x = arr[l]

                while (i < j) {
                    while (i < j && arr[j] >= x) {
                        j--
                    }

                    if (i < j) {
                        arr[i] = arr[j]
                    }

                    while (i < j && arr[i] <= x) {
                        i++
                    }

                    if (i < j) {
                        arr[j] = arr[i]
                    }
                }

                arr[i] = x

                quickSort(arr, l, i - 1)
                quickSort(arr, i + 1, h)
            }
        }

线性表

链表

链表是一组节点组成的集合,每个节点都使用一个对象的引用来指向它的后一个节点。指向另一节点的引用讲做链。 avatar

  • 链表
var MyLinkedList = function() {
            this.head = null
            this.size = 0
        };

        /**
         * Get the value of the index-th node in the linked list. If the index is invalid, return -1.
         * @param {number} index
         * @return {number}
         */
        function Node(val) {
            this.val = val
            this.next = null
        }

        MyLinkedList.prototype.get = function(index) {
            let val = -1
            if (this.size <= index) {
                return val
            }

            let cur = this.head

            for (let i = 0; i < this.size; i++) {
                if (i != index) {
                    cur = cur.next
                } else {
                    val = cur ? cur.val : -1
                    break
                }
            }

            return val
        };

        /**
         * Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list.
         * @param {number} val
         * @return {void}
         */
        MyLinkedList.prototype.addAtHead = function(val) {
            this.addAtIndex(0, val)
        };

        /**
         * Append a node of value val to the last element of the linked list.
         * @param {number} val
         * @return {void}
         */
        MyLinkedList.prototype.addAtTail = function(val) {
            this.addAtIndex(this.size, val)
        };

        /**
         * Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted.
         * @param {number} index
         * @param {number} val
         * @return {void}
         */
        MyLinkedList.prototype.addAtIndex = function(index, val) {
            let node = new Node(val)
            if (index - 1 > this.size) {
                return
            } else {
                if (index < 0) {
                    index = 0
                }

                if (index == 0) { // 边界
                    let cur = this.head
                    node.next = cur
                    this.head = node
                } else {
                    let cur = this.head
                    for (let i = 0; i < this.size; i++) {
                        if (i == (index - 1)) {
                            node.next = cur.next
                            cur.next = node
                            break
                        }

                        cur = cur.next
                    }
                }
            }

            this.size++
        };

        /**
         * Delete the index-th node in the linked list, if the index is valid.
         * @param {number} index
         * @return {void}
         */
        MyLinkedList.prototype.deleteAtIndex = function(index) {
            if (index < this.size) {
                if (index == 0) {
                    this.head = this.head.next
                } else {
                    let cur = this.head
                    for (let i = 0; i < this.size; i++) {
                        if (index - 1 == i) {
                            cur.next = cur.next.next
                            break
                        }

                        cur = cur.next
                    }
                }

                this.size--
            }
        };
        
        let linkedList = new MyLinkedList()
        linkedList.addAtHead(7)
        linkedList.addAtHead(2)
        linkedList.addAtHead(3)
        linkedList.addAtTail(1)
        linkedList.addAtTail(2)
        linkedList.addAtTail(3)
        linkedList.addAtIndex(3,0)

        console.log(linkedList.get(1))
        linkedList.deleteAtIndex(1)
        console.log(linkedList.get(1))
        console.log(linkedList.head)
  • 反转链表

非递归

        // 反转链表
        function reverseLinkedList(nodeList) {
            let linkedList = new NodeList()
            let node = nodeList.first
            let next

            for (let i = 0; i < nodeList.size; i++) {
                next = node.next
                node.next = linkedList.first
                linkedList.first = node
                node = next

                linkedList.size++
            }

            console.log(linkedList.toString())
        }

递归实现

var reverseList = function(head) {
    if (head === null || head.next === null) return head
    let node = reverseList(head.next)
    head.next.next = head
    head.next = null
    
    return node
};
  • 环形链表

判断环形链表的核心思路:快慢指针

var hasCycle = function(head) {
    let slow = head
    let fast = head

    while(fast != null && fast.next != null) {
        slow = slow.next
        fast = fast.next.next
        
        if (slow == fast) {
            return true
        }
    }

    return false
};