zoukankan      html  css  js  c++  java
  • 数据结构和算法学习笔记(1)

    抓紧时间学习了,一步一个脚印,绝不松懈,这是本系列的第一篇,算法和数据结构是程序员你的必修课,面试看懂别人复杂的程序这些都是基本功,需要加强,所以有了下面的文章,2010.2.4 6.24分

    数据结构和算法学习笔记(1)

    首先给出一个最基本的结论:我们做算法和结构其实说白了,就是空间换时间,或者时间换空间。要看需求了。一般的情况下,大家都喜欢空间换时间。因为内存大嘛,总是用啊用不完。

    最基本的一些结构就略过了,从栈开始吧。

    1 堆栈

    堆栈即为栈,下面是一个数组实现的栈:该栈固定大小,即存放的元素最大值固定,数组第0位表示栈底。如果需要存入不确定数量的数据,请使用链表来实现栈。

    class Stack

             {

             public:

                       Stack(int iAmount = 10);   //设置栈所在数组的最大值

                       ~Stack();

                       int Pop(int&iVal);       //出栈

                       int Push(intiVal);       //入栈

                       int Top(int&iVal);     //栈顶元素

             private:

                       int *m_pData;       //栈所在数据首地址。。简单起见用int应该改用泛型

                       int m_iCount;            //使用的元素个数

                       int m_iAmount;        //栈最大元素个数

             };

    Stack::Stack(int iAmount)

             {

             m_pData= new int[iAmount];//连续的数组,其中固定大小来划分栈中元素

             m_iCount= 0;                 //初始化栈内元素为0个

             m_iAmount= iAmount;

             }

    Stack::~Stack()

             {

             delete m_pData;

             }

    int Stack::Pop(int&iVal)

             {

             if(m_iCount>0)

                       {

                       --m_iCount;                  //栈内元素--

                       iVal= m_pData[m_iCount];    

                       return 1;

                       }

             return 0;

             }

    int Stack::Push(int iVal)

             {

             if(m_iCount<m_iAmount)

                       {

                       m_pData[m_iCount]= iVal;

                       ++m_iCount;

                       return 1;

                       }

             return 0;

             }

    int Stack::Top(int&iVal)

             {

             if(m_iCount>0 && m_iCount<=m_iAmount)

                       {

                       iVal= m_pData[m_iCount-1];

                       return 1;

                       }

             return 0;

             }

    2 队列

    下面是一个利用队列来对树进行广度优先检索。

    广度优先区别于深度优先,即优先遍历最靠近根节点的各个节点:

    我们的算法是:
    1,根节点入队
    2,出队一个节点,算一次遍历,直到队列为空
    3,将刚出队的节点的子节点入队
    4,转到2

    队列的状况如下图:

    // The Node

    //////////////////////////////////////////////////////////////////////////

    struct Node  //树的每个节点

             {

             Node(char cChar, intiSubNodeNum=0);

             ~Node();

             char m_cChar;               //该节点编号例如(A.B.C……)

             int m_iSubNodeNum;  //该节点的子节点数目

             Node**m_arrNodePointer; //指向子节点

             };

     

    Node::Node(char cChar, intiSubNodeNum)

             {

             m_cChar= cChar;

             m_iSubNodeNum= iSubNodeNum;

             if(iSubNodeNum!=0)

                       m_arrNodePointer= new Node*[iSubNodeNum];

             else

                       m_arrNodePointer= NULL;

             }

     

    Node::~Node()

             {

             if(m_arrNodePointer!=NULL)

                       delete[] m_arrNodePointer;

             }

     

    // The Queue

    //////////////////////////////////////////////////////////////////////////

    class Queue

             {

             public:

                       Queue(int iAmount=10);

                       ~Queue();

                       //return 0 means failed, return 1 means succeeded.

                       int Enqueue(Node* node);

                       int Dequeue(Node* & node);

             private:

                       int m_iAmount;

                       int m_iCount;

                       Node**m_ppFixed; //The pointer array to implement thequeue.

                       int m_iHead;

                       int m_iTail;

             };

     

    Queue::Queue(int iAmount)

             {

             m_iCount= 0; //队列中已经使用的元素

             m_iAmount= iAmount; //队列最大元素个数

             m_ppFixed= new Node*[iAmount]; //初始化一个数组保存元素

             m_iHead= 0;   //头位置,即取元素位置,在数组开头

             m_iTail= iAmount-1; //插入元素位置。在数组尾部

             }

     

    Queue::~Queue()

             {

             delete[] m_ppFixed;

             }

     

    int Queue::Enqueue(Node* node)

             {

             if(m_iCount<m_iAmount)

                       {

                       ++m_iTail;

                       if(m_iTail > m_iAmount-1)

                                m_iTail= 0;

                       m_ppFixed[m_iTail]= node;

                       ++m_iCount;

                       return 1;

                       }

             else

                       return 0;

             }

     

    intQueue::Dequeue(Node* & node)

             {

             if(m_iCount>0)

                       {

                       node= m_ppFixed[m_iHead];

                       ++m_iHead;

                       if(m_iHead > m_iAmount-1)

                                m_iHead= 0;

                       --m_iCount;

                       return 1;

                       }

             else

                       return 0;

             }

     

    // Main

    //////////////////////////////////////////////////////////////////////////

    int main(int argc, char* argv[])

             {

             //Construct the tree.

             NodenA('A', 3);

             Node nB('B',2);

             Node nC('C');

             Node nD('D',3);

             Node nE('E');

             Node nF('F',2);

             Node nG('G');

             Node nH('H',1);

             Node nI('I');

             Node nJ('J');

             Node nK('K');

             Node nL('L');

             nA.m_arrNodePointer[0] = &nB; //指向子节点

             nA.m_arrNodePointer[1] = &nC;

             nA.m_arrNodePointer[2] = &nD;

             nB.m_arrNodePointer[0] = &nE;

             nB.m_arrNodePointer[1] = &nF;

             nD.m_arrNodePointer[0] = &nG;

             nD.m_arrNodePointer[1] = &nH;

             nD.m_arrNodePointer[2] = &nI;

             nF.m_arrNodePointer[0] = &nJ;

             nF.m_arrNodePointer[1]= &nK;

             nH.m_arrNodePointer[0]= &nL;

             Queueque;

             que.Enqueue(&nA);  //根节点A入队列

             Node*pNode;

             while (que.Dequeue(pNode)==1)  //从队列中取出一个元素(第一次自然是A);第二次取出B

                       {

                       printf("%c ", pNode->m_cChar);   //第一次输出A;第二次输出B

                       int i;

                       for(i=0; i<pNode->m_iSubNodeNum; i++)  //第一次为A的三个子节点B,C,D入队列;第二次将B的子节点E,F加入队列。

                                {

                                que.Enqueue(pNode->m_arrNodePointer[i]);

                                }

                       }

             return 0;

             }

    3 二分查找

    //参数:有序数组、要查找的数字、最低位(一般为)、最高位(一般为数组最大下标)

    static int

    dichotomy_search(short a[], ems_int32 num, ems_int32 low, ems_int32high)

             {

             ems_int32mid;

             if(low > high)

                       {

                       return FATAL_ERROR; //没有查到。。

                       }

             mid= (low + high)/2;     //中间

             if(num == a[mid])

                       {

                       return mid;   //找到

                       }

             else

                       {

                       if(num < a[mid])  //在左边

                                {

                                return dichotomy_search(a, num, low, mid-1);

                                }

                       else  //在右边

                                {

                                return dichotomy_search(a, num, mid+1, high);

                                }

                       }

             }

    4 散列

    一般就是一个数组,通过散列算法定位到数组某个元素,当然这样可能会重复,你可以继续散列,或者做链表等等方法来处理。

    1,除法散列法
    最直观的一种,上图使用的就是这种散列法,公式:
    index = value % 16
    学过汇编的都知道,求模数其实是通过一个除法运算得到的,所以叫“除法散列法”。

    2,平方散列法
    求index是非常频繁的操作,而乘法的运算要比除法来得省时(对现在的CPU来说,估计我们感觉不出来),所以我们考虑把除法换成乘法和一个位移操作。公式:
    index = (value * value) >> 28
    如果数值分配比较均匀的话这种方法能得到不错的结果,但我上面画的那个图的各个元素的值算出来的index都是0——非常失败。也许你还有个问题,value如果很大,value * value不会溢出吗?答案是会的,但我们这个乘法不关心溢出,因为我们根本不是为了获取相乘结果,而是为了获取index。

    可能有其他类型的输入,随机应变吧。

    5 树

    深度优先遍历又可分为:前序遍历(Preorder Traversal),后序遍历(Postorder Traversal)和中序遍历(Inorder Traversal),其中中序遍历只有对二叉树才有意义

    //////////////////////////////////////////////////////////////////////////

    struct TreeNode //树节点

             {

             char m_cVal;  //节点值

             TreeNode*m_pLeft; //左右子节点

             TreeNode*m_pRight;

             TreeNode(char cVal);

             };

     

    TreeNode::TreeNode(char cVal)

             {

             m_cVal= cVal;

             m_pLeft= 0;

             m_pRight= 0;

             }

             //      A

             //    /   \

             //  /     \

             // B       C

             //   \     / \

             //   D   E   F

             //     \       \

             //     G       H

             //             / \

             //            I  J

             //           / \

             //          K  L

    // TreeNode

    中序遍历:从A开始

    TreeNode p = 根节点A;

             while( p != NULL)

                       {

                       if(p有左节点)

                                {

                                P入栈 //1

                                p= p的左子节点//2

                                }

                       else if (p有右子节点)

                                {

                                输出P //1

                                p= p的右子节点//2

                                }

                       else //P无子节点了

                                {

                                输出P

                                如果栈里有值,弹出一个

                                如果栈里没有值了     break;

                                }

                       }

    另外可以用递归实现,很简单。

    如果需要查找和插入都很快,那么无疑应该选用二叉查找树。但是这种树的删除效率略低。如果用在只需要查找和插入的地方,比如构建属性表,空间信息数据则非常好。

    但是,应该了解,二叉搜索树如果根节点的值选的不好或者插入的顺序不好,会使树非常之深,导致搜索插入效率急剧降低。那么就需要平衡二叉搜索树了。平衡二叉树的查找和删除和二叉搜索树一模一样。关键是构造的时候算法问题,主要分为4种情况,具体的算法可网上参阅。

    6 二叉堆

    一种特殊的队列,总是最小的元素先出。插入和取出都很快速,复杂度logn

    基本概念:

     1 二叉堆是一种特殊的完全二叉树,完全二叉树的最大特点在于不需要指针来表明左右节点。可以直接利用数组来保存完全二叉树,利用偏移来找到需要的元素。

    2 其中最小的元素总在根节点。

    3 入队原则:将新加入的元素放到树(数组)的最后面,然后依次和父节点比较,如果比父节点小,则交换位置。如此循环,知道无法交换为止。

    4 出队原则:不好写。略

    //交换个整形

    //example:SWAP_TWO_INT(7,8)

    // a = 0111^1000 =0000

    // b = 1000^0000 =1000

    // a = 0000^1000 =0111

    #define SWAP_TWO_INT(a, b) \

             a^=b;b^=a; a^=b;

     

    class CBinaryHeap

             {

             public:

                       CBinaryHeap(int iSize = 100);

                       ~CBinaryHeap();

                       int Enqueue(intiVal);

                       int Dequeue(int&iVal);

                       int GetMin(int&iVal);

    #ifdef _DEBUG

                       void PrintQueue();

    #endif

             protected:

                       int *m_pData; //保存二叉堆的数组

                       int m_iSize;    //二叉堆最大容量

                       int m_iAmount; //目前数目

             };

     

    CBinaryHeap::CBinaryHeap(int iSize)

             {

             m_pData= new int[iSize];

             m_iSize= iSize;

             m_iAmount= 0;

             }

     

    CBinaryHeap::~CBinaryHeap()

             {

             delete[] m_pData;

             }

     

    #ifdef _DEBUG

    int CBinaryHeap::Enqueue(intiVal) //入队列

             {

             if(m_iAmount==m_iSize)

                       return 0;

             //Put this value to the end of the array.

             m_pData[m_iAmount]= iVal; //值放到数组最后,即二叉堆的最后

             ++m_iAmount;

             int iIndex = m_iAmount - 1;

             while(m_pData[iIndex] < m_pData[(iIndex-1)/2]) //循环和上一层比较,如果小于上一层则交换位置。

                       {

                       //Swap the two value

                       SWAP_TWO_INT(m_pData[iIndex],m_pData[(iIndex-1)/2])

                                iIndex= (iIndex-1)/2;//完全二叉树在数组中的位置固定

                       }

             return 1;

             }

    #endif

     

    int CBinaryHeap::Dequeue(int&iVal)//出队列

             {

             if(m_iAmount==0)

                       return 0;

             iVal= m_pData[0]; //返回根节点

             int iIndex = 0;

             while (iIndex*2 < m_iAmount)

                       {

                       int iLeft = (iIndex*2+1 <m_iAmount)?(iIndex*2+1):0;

                       int iRight = (iIndex*2+2 <m_iAmount)?(iIndex*2+2):0;

                       if(iLeft && iRight) //Both left and right exists. 将根节点的子节点中较小的元素和根节点交换。

                                {

                                if(m_pData[iLeft]<m_pData[iRight])

                                         {

                                         SWAP_TWO_INT(m_pData[iIndex],m_pData[iLeft])

                                                   iIndex= iLeft;

                                         }

                                else

                                         {

                                         SWAP_TWO_INT(m_pData[iIndex],m_pData[iRight])

                                                   iIndex= iRight;

                                         }

                                }

                       else if(iLeft) //The iRight must be 0

                                {

                                SWAP_TWO_INT(m_pData[iIndex],m_pData[iLeft])

                                         iIndex= iLeft;

                                break;

                                }

                       else

                                {

                                break;

                                }

                       }

             //Move the last element to the blank position.

             //Of course, if it is the blank one, forget it.

             if(iIndex!=m_iAmount-1)

                       {

                       m_pData[iIndex]= m_pData[m_iAmount-1]; //将最后一个元素移到目前根节点所在的位置

                       //Try to move this element to the top as high as possible.

                       while(m_pData[iIndex] < m_pData[(iIndex-1)/2])

                                {

                                //Swap the two value

                                SWAP_TWO_INT(m_pData[iIndex],m_pData[(iIndex-1)/2])

                                         iIndex= (iIndex-1)/2;

                                }

                       }

             --m_iAmount;

             return 1;

             }

     

    int CBinaryHeap::GetMin(int&iVal)

             {

             if(m_iAmount==0)

                       return 0;

             iVal= m_pData[0];

             return 1;

             }

     

    void CBinaryHeap::PrintQueue()

             {

             int i;

             for(i=0; i<m_iAmount; i++)

                       {

                       printf("%d ", m_pData[i]);

                       }

             printf("\n");

             }

     

    int main(int argc, char* argv[])

             {

             CBinaryHeapbh;

             bh.Enqueue(4);  //入队

             bh.Enqueue(1);

             bh.Enqueue(3);

             bh.Enqueue(2);

             bh.Enqueue(6);

             bh.Enqueue(5);

    #ifdef _DEBUG

             bh.PrintQueue();

    #endif

             int iVal;

             bh.Dequeue(iVal);//出队

             bh.Dequeue(iVal);

    #ifdef _DEBUG

             bh.PrintQueue();

    #endif

             return 0;

           }

    7 排序算法

    7.1 //冒泡排序

    void BubblerSort(int *pArray,int iElementNum)

             {

             int i, j, x;

             for(i=0; i<iElementNum-1; i++)  //

                       {

                       for(j=0; j<iElementNum-1-i; j++)

                                {

                                if(pArray[j]>pArray[j+1])

                                         {

                                         x= pArray[j];

       &nbs p;                                 pArray[j]= pArray[j+1];

                                         pArray[j+1]= x;

                                         }

                                }

                       }

             }

    //内部每一次循环将数组中最大的元素移到最后

    //外部循环n-1次排序完毕,

    //复杂度n*n/2 = n*n

     

    7.2 //直接插入排序

    //第一次取数组前个排序,第二次将第三个元素插入前面已经排好序的个数里面

    //第三次将第个元素插入到前面个已排好序的元素里面

    void StraightInsertionSort(int*pArray, int iElementNum)

             {

             int i, j, k;

             for(i=0; i<iElementNum; i++)

                       {

                       int iHandling = pArray[i];

                       for(j=i; j>0; j--)   //循环比较查找要插入的位置。。

                                {

                                if(iHandling>=pArray[j-1]) //找到要插入的位置

                                         break; 

                                }

                       for(k=i; k>j; k--) //将要插入的元素插入到指定位置,后面的元素依次顺移

                                pArray[k]= pArray[k-1];

                       pArray[j]= iHandling;

                       }

             }

     

    7.3 //二分插入排序

    //和直接插入排序基本一样,只是在插入元素的时候利用了二分查找

    void BinaryInsertionSort(int*pArray, int iElementNum)

             {

             int i, j, k;

             for(i=0; i<iElementNum; i++)

                       {

                       int iHandling = pArray[i];

                       int iLeft = 0;

                       int iRight = i-1;

                       while(iLeft<=iRight) //二分查找要插入的位置

                                {

                                int iMiddle = (iLeft+iRight)/2;

                                if(iHandling < pArray[iMiddle])

                                         {

                                         iRight= iMiddle-1;

                                         }

                                else if(iHandling> pArray[iMiddle])

                                         {

                                         iLeft= iMiddle+1;

                                         }

                                else

                                         {

                                         j= iMiddle + 1;

                                         break;

                                         }

                                }

                       if(iLeft>iRight)

                                j= iLeft; //如果没有找到,即不需要移动位置了。

                       for(k=i; k>j; k--)

                                pArray[k]= pArray[k-1];

                       pArray[j]= iHandling;

                       }

           }

    //直接选择排序

    //每循环一次把最大的元素取出来和最后一个元素交换

    void StraightSelectionSort(int*pArray, int iElementNum)

             {

             int iEndIndex, i, iMaxIndex, x;

             for(iEndIndex=iElementNum-1; iEndIndex>0;iEndIndex--)

                       {

                       for(i=0, iMaxIndex=0; i<iEndIndex; i++) //找出最大的元素

                                {

                                if(pArray[i]>=pArray[iMaxIndex])

                                         iMaxIndex= i;

                                }

                       x= pArray[iMaxIndex];  //和最后一个元素交互

                       pArray[iMaxIndex]= pArray[iEndIndex];

                       pArray[iEndIndex]= x;

                       }   

           }

    //快速排序,利用递归。

    void QuickSort(int *pArray, int iElementNum)

             {

             int iTmp;

     

             //Select the pivot make it to the right side.

             int& iLeftIdx = pArray[0];

             int& iRightIdx = pArray[iElementNum-1];

             int& iMiddleIdx = pArray[(iElementNum-1)/2];

             if(iLeftIdx>iMiddleIdx)

                       {

                       iTmp= iLeftIdx;

                       iLeftIdx= iMiddleIdx;

                       iMiddleIdx= iTmp;

                       }

             if(iRightIdx>iMiddleIdx)

                       {

                       iTmp= iRightIdx;

                       iRightIdx= iMiddleIdx;

                       iMiddleIdx= iTmp;

                       }

             if(iLeftIdx>iRightIdx)

                       {

                       iTmp= iLeftIdx;

                       iLeftIdx= iRightIdx;

                       iRightIdx= iTmp;

                       }   //1:将左中右个元素的处于中间大小的元素放到数组的最后面,设为iPivot,为最开始的基础比较数据。

    //2: 从数组第一个开始往后找到第一个大于iPivot的值,设为iLeft;从数组倒数第二个开始往前找到第一个小于iPivot的值iRight。然后交换iLeft和iRight。

             //Make pivot's left element and right element.

             int iLeft = 0;

             int iRight = iElementNum-2;

             int& iPivot = pArray[iElementNum-1];

             while (1)

                       {

                       while (iLeft<iRight &&pArray[iLeft]<iPivot) ++iLeft;

                       while (iLeft<iRight &&pArray[iRight]>=iPivot) --iRight;

                       if(iLeft>=iRight)

                                break;

                       iTmp= pArray[iLeft];

                       pArray[iLeft]= pArray[iRight];

                       pArray[iRight]= iTmp;  //交换iLeft和iRight

                       }

     

             //Make the i

             if(pArray[iLeft]>iPivot)  //这次交换有2个目的 1 小的在前面 2使比较的元素更接近平均值

                       {

                       iTmp= pArray[iLeft];

                       pArray[iLeft]= iPivot;

                       iPivot= iTmp;

                       }

     

             if(iLeft>1)

                       QuickSort(pArray,iLeft); //对前半部分排序

     

             if(iElementNum-iLeft-1>=1)

                       QuickSort(&pArray[iLeft+1],iElementNum-iLeft-1); //对后半部分排序

           }

    //桶排序,例如buckets[100]=10 表示pArray中数值为100的元素有10个

    void BucketSort(int *pArray, int iElementNum)
    {
        int buckets[RAND_MAX]; // RAND_MAX 这个值需要囊括所有的pArray中的元素,不好把握。
        memset(buckets, 0, sizeof(buckets));
        int i;
        for(i=0; i<iElementNum; i++)
        {
            ++buckets[pArray[i]-1];
        }

        int iAdded = 0;
        for(i=0; i<RAND_MAX; i++)
        {
            while((buckets[i]--)>0)
            {
                pArray[iAdded++] = i;
            }
        }
    }

  • 相关阅读:
    【js】右下角浮动窗口
    malefile
    跟我一起学习VIM
    Linux服务器开发初步
    如何学习Linux
    什么是Java序列化?如何实现序列化?
    java微信工众号开发
    史上最全最强SpringMVC详细示例实战教程
    Hibernate注解方法使用总结
    Hibernate注解
  • 原文地址:https://www.cnblogs.com/SuperXJ/p/1663888.html
Copyright © 2011-2022 走看看