【算法】计算机算法—一数据结构

本文详细阐述了数据结构如栈、二叉树、查找树等及其操作,以及经典算法如汉诺塔、堆排序、二分查找的应用与实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、绪论

1. 汉诺塔问题 (递归)

塔 A B  C , 为把最大的圆盘置于塔B的底部,需将其余的n-1移动到塔C, 将最大的圆盘移动到塔B, 通过函数TowersOfHanoi(n, A, B, C),可以看出n个圆盘的问题可以根据两次n-1个圆盘问题的解来表示。

void TowerOfHanoi(int n, tower A, tower B, tower C)

{

if (n)

{

TowerOfHanoi(n-1, A, C, B);

TowerOfHanoi(n-1, C, B, A);

}

}


第二章 基本数据结构

1. 栈

最简单一维数组stack[MAXSIZE],top变量指向栈中最顶元素,检验栈是否为空if (top < 0), 检验是否已满 if (top >= (MAXSIZE - 1)).

class Stack

{

private:

     int top, MAXSiZE;

    Type *stack;

public:

    Stack (int Msize): MaxSize(Msize)

    {    stack = new Type[MaxSize]; top = -1;   }

    ~Stack()

     {    delete []stack;   }


     inline bool Add(Type item)

     {

           if (StackFulle())

                   return false;

           else

           {

                   stack[++top] = item;

            }

            return ture;

     }


     inline bool delete(Type item)

     {

            if (StackEmpty())

                   return false;

            else

                   item = stack[top--];

             return true;

     }

    

     inline bool StackEmpty

     {

              if (top < 0)

                     return true;

              else

                     return false;

     }

     .........

}

////////////链栈

class Stack

{

private:

      struct Node

      {

              Type data;  struct Node *link;

      }

      struct Node *top;

public:

      Stack ()

      {     top = NULL;  }

      ~Stack ()

      {

              struct Node *temp;

              while (top)

              {

                    temp = top; top = top -> link;

                    delete temp;

              }

      }

      bool Add (const Type item)'

      bool Detelte (Type &item);

      inline bool StackEmpty()

      {

               if (top)

                      return false;

               else

                      return true;

      }

}

//////////add

bool Stack::Add(conset Type item)

{

         struct Node *temp = new Node;

         if (temp)

         {

                 temp->link = top;

                 top =temp;

         }

          else

          {

                 printf("allocal false");

                 return false;

        }

         return true;

}

////////////delete

bool Struct::Delete (Type &item)

{

         if (StackEmpty())

                 return false;

         else

         {

                  struct Node *temp;

                  item = top -> data; temp =top;

                  top = top->link;

                  delete temp

         }

         return true;

}


3. 二叉查找树

class TreeNode

{

      friend class BSTree;

private:

      TreeNode *lchild, *rchild;

      Type data;

}

class BSTree

{

private:

     TreeNode *tree;

     TreeNode *Search(TreeNode *t, Type t);

public:

     BSTree() { tree= NULL;}

    ~BSTree() {  delete tree; }

    TreeNode *Search(Type x);

    TreeNode ISearch(Type x);


     void Insert(Type x);

     void Delete(Type x);

}

//二叉树查找递归  左子树都< 根节点, 右子树都>根节点

TreeNode *BSTree::Search(Type x)

{

     return Search(tree, x);

}

TreeNode *BSTree::Search(TreeNode *t, Type x)

{

    if (t == NULL)

           return 0

    else if (x == t->data)

           return t;

    else if (x < t->data)

           return Search(t->lchild, x);

     else

           return Search (t->rchild, x);

}

///////////二叉树的插入  (检索失败则在检索位置插入该元素)

void BSTree::Insert(Type x)

{

     bool found = false;

     TreeNode *p = tree, *q;


      while ( (p)  && (! found) )

      {

              q = p;  //save p

              if ( x == p->data)

                     found = true;

               else if ( x < p->data)

                     p = p->lchild;

                else

                     p = p->rchild;

       }

       if (!found)

       {

               p = new TreeNode;

               p->lchild = NULL; p->rchild = NULL; p-data = x;

               if (tree)

               {

                         if (x < q->data)  q->lchid = p;

                         else    q-rchild = p;

               }

               else

                        tree = p'

       }

}

/////////二叉查找树的删除

1. 删除叶子, 将双亲节点域设为NULL。

2. 只有一个孩子非叶子节点,只需孩子节点代替原来位置。

3. 两个孩子的非叶子节点,一种是以左子树中具有最大关键字的节点取代该节点,二是以右子树具有最小关键字节点取代。



4. 堆 O(log n)

class Heap

{

private:

         Type *array;

         int MaxSIze, Nel;

         void Adjust(Type a[], int i, int n);

public:

        Heap(int MSize) : MaxSize(MSize)

        {  array = new Type[MaxSize + 1];  Nel = 0;  }

        ~Heap ()  {  delete []array;  }

        bool Insert(Type item);

        bool DelMax(Type &item);

}

///堆的插入 (最坏情况,while迭代的次数与堆的层次成正比)

void Heap::Insert(Type item)

{

       int i = ++Nel;

       if (i == MaxSize)

              return false;

       while ( (i > 1)  && (arrary[i/2] < item))

       {

                 array[i] = array[i / 2];  i/=2;

        }

        array[i] = item;

        return true;

}

////堆的删除

void Heap::Adjust(Type a[], int i, int n)

{

      int j = 2 * i; item = a[i];

      while (j <= n)

     {

              if ( (j <  n) && a[j] < a[j+1] )  j++;

              if (item >= a[j]) break;

              a[j / 2] = a[j];  j *=2;

      }

     a[j / 2] = item;

}

bool Heap::DelMax(Type &item)

{

         if (! Nel)

               return false;

         item = array[1];  array[1] = array[Nel--];

         Adjust(array, 1, Nel);

         return true;

}


五. 二叉树

**
 * Definition for binary tree
 */
typedef struct TreeNode { //the struct of NODE
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
}BitNode, *BitTree;


class BinaryTee {
Private:
    BitTree T;
public:
    int CreateBitTree(BitTree &T, ifstram &in);
    //递归遍历
    void preOrder(BitTree &T);
    void inOrder(BitTree &T);
    void postOrder(BitTree &T);
    void levelOrder(BitTree &T);




};


//先序次序输入,构造二叉树,空格表示空树
void createBitTree(BitTree &T) {
    char ch;
    scanf("%c", &ch);
    if (ch == '')
        T = NULL;
    else {
        T = (BitNode *)malloc(sizeof(BitNode));
        T->data = ch;


        createBitTree(T->left);
        createBitTree(T->right);
    }
}


六. 链表


/////////////////////////////////////////////////////////////从尾到头打印链表

void printListRevers(ListNode *pHead) {

       stack<ListNode *> nodes;

       ListNode *pNode = pHead;

       while (pNode != NULL) {

               nodes.push(pNode);

               pNode = pNode->next;

       }

       while (!nodes.empty())

       {

                pNode = nodes.top();

                printf();

                nodes.pop();

        }

}

//递归

void print_list_reverse(ListNode *pHead) {

       if (pHead != NULL) {

              if (pHead->next !=NULL)

              {

                       printf_list_reverse(pHead->next);

              }

              printf();

       }

}


二叉搜索树(二叉排序树)


二叉排序树 binary sort tree 或称二叉搜寻树是一种动态树表,中序遍历即可获得一个关键字的有序序列。一个无序序列可以通过构造一棵二叉排序树而变成有序系列。通过对其限制或扩展可以进一步构造AVL 树及B 树。
(1) 对二叉排序树的插入,都是在二叉树上新建新的叶子结点。对于不同的插入顺序,形成的二叉排序树是不同的。
(2) 利用二叉排序树查找,最好情况下与在折半查找中所形成的判定树查找效果相同,而最坏情况下为(n+1)/2,为顺序查找相同。期望O(logn),最坏O(n);即可引入平衡树的概念。
(3) 而对其删除操作,若删除为叶子结点,则可将其直接删除,修改器双亲结点指针即可;

     若删除的结点仅有左子树或右子树,则直接另其双亲结点指向其左子树或右子树即可;

     若删除的结点左右子树均为非空,则首先找到删除结点P 左子树PL的右子树中的最后一个点S(PL 中的最大值),可删除P 后另PL 代替P,而PR 连到S 的右子树中图c 或者将S 代替P,并将SL 连到原来S 的位置图d。



//建立

  1. void insert(PTRNODE &root, int value)  
  2. {  
  3.     if(root == NULL)  
  4.         root = new NODE(value);  
  5.     else  
  6.     {     
  7.         if(value < root->value)  
  8.             insert(root->left, value);  
  9.         else if(value > root->value)  
  10.             insert(root->right, value);  
  11.         else  
  12.             cout << "duplicated value" << endl;  
  13.     }  
  14. }  





一 数组


1.1 Search in Rotated Sorted Array

     Suppose a sorted array is rotated at some pivot unknown to you beforehand.

    (i.e., 0 1 2 4 5 6 7 might become 4 5 6 7 0 1 2).

    You are given a target value to search. If found in the array return its index, otherwise return -1.

    You may assume no duplicate exists in the array.

二分查找,在于确定左右边界,数组可能有以下三种情况:



1.2 Search in Rotated

     题目:给定两个已经排序好的数组(可能为空),找到两者所有元素中第k大的元素。另外一种更加具体的形式是,找到所有元素的中位数。本篇文章我们只讨论更加一般性的问题:如何找到两个数组中第k大的元素?
    方案1:假设两个数组总共有n个元素,那么显然我们有用O(n)时间和O(n)空间的方法:用merge sort的思路排序,排序好的数组取出下标为k-1的元素。
    方案2:不需要“排序”这么复杂的操作的,因为仅仅需要第k大的元素。可以用一个计数器,记录当前已经找到第m大的元素了。同时使用两个指针pA和pB,分别指向A和B数组的第一个元素。使用类似于merge sort的原理,如果数组A当前元素小,那么pA++,同时m++。如果数组B当前元素小,那么pB++,同时m++。最终当m等于k的时候,就得到了我们的答案——O(k)时间,O(1)空间。
    但是,当k很接近于n的时候,这个方法还是很费时间的。当然,我们可以判断一下,如果k比n/2大的话,我们可以从最大的元素开始找。但是如果我们要找所有元素的中位数呢?时间还是O(n/2)=O(n)的。有没有更好的方案呢?
    考虑从k入手。如果我们每次都能够剔除一个一定在第k大元素之前的元素,那么需要进行k次。但是如果每次我们都剔除一半呢?所以用这种类似于二分的思想,我们可以这样考虑:

   数组A和B的元素个数都大于k/2,我们比较A[k/2-1]和B[k/2-1]两个元素,这两个元素分别表示A的第k/2小的元素和B的第k/2小的元素。这两个元素比较共有三种情况:

   A[k/2-1]<B[k/2-1],表示A[0]到A[k/2-1]的元素都在A和B合并之后的前k小的元素中。换句话说,A[k/2-1]不可能大于两数组合并之后的第k小值,所以我们可以将其抛弃。

还需要考虑几个边界条件:

  • 如果A或者B为空,则直接返回B[k-1]或者A[k-1];
  • 如果k为1,我们只需要返回A[0]和B[0]中的较小值;
  • 如果A[k/2-1]=B[k/2-1],返回其中一个;
  1. double findKth(int a[], int m, int b[], int n, int k)  
  2. {  
  3.     //always assume that m is equal or smaller than n  
  4.     if (m > n)  
  5.         return findKth(b, n, a, m, k);  
  6.     if (m == 0)  
  7.         return b[k - 1];  
  8.     if (k == 1)  
  9.         return min(a[0], b[0]);  
  10.     //divide k into two parts  
  11.     int pa = min(k / 2, m), pb = k - pa;  
  12.     if (a[pa - 1] < b[pb - 1])  
  13.         return findKth(a + pa, m - pa, b, n, k - pa);  
  14.     else if (a[pa - 1] > b[pb - 1])  
  15.         return findKth(a, m, b + pb, n - pb, k - pb);  
  16.     else  
  17.         return a[pa - 1];  
  18. }  


1.3 Merge Sorted Array

      Given two sorted integer arrays A and B, merge B into A as one sorted array. 

      问题描述:给两个有序的整型数组A和B,A数组长度为m,B数组长度为n,前提假设A有足够大的空间去容纳B。

void merge(vector<int>& A, int m, vector<int>& B, int n) { 
    int ia = m - 1, ib = n - 1, icur = m + n - 1; 
    while(ia >= 0 && ib >= 0) { 
        A[icur--] = A[ia] >= B[ib] ? A[ia--] : B[ib--]; 
    } 
    while(ib >= 0) { 
        A[icur--] = B[ib--]; 
    } 
}



二 链表


2.1 链表逆转

void list_reverse(ListNode *pHead) {
	if (pHead == NULL || pHead->next == NULL)
		return;

	ListNode *p = pHead->next;
	ListNode *temp = pHead->next;
	pHead->next = NULL;

	while (p != NULL) {
		p->next = pHead;
		pHead = p;
		temp = temp->next;
		p = temp;
	}
}


2.2  Swap Nodes in Pairs

    两个节点的链表逆转


2.3 Linked List Cycle

    快慢指针的经典应用。只需要设两个指针,一个每次走一步的慢指针和一个每次走两步的快指针,如果链表里有环的话,两个指针最终肯定会相遇。

class Solution {
public:
    bool hasCycle(ListNode *head) {
        ListNode *slow = head, *fast = head;
        while (fast && fast->next) {
            slow = slow->next;
            fast = fast->next->next;
            if (slow == fast) return true;
        }
        return false;
    }
};

2.4 Linked List Cycle II 单链表中的环之二

F走的路程应该是S的两倍

S = x + y

F = x + y + z + y = x + 2y + z

2*S = F

2x+2y = x + 2y + z

得到x = z


也就是从head到环开始的路程 = 从相遇到环开始的路程

只要S和F相遇了,我们拿一个从头开始走,一个从相遇的地方开始走

两个都走一步,那么再次相遇必定是环的开始节点!


ListNode *detectCycle(ListNode *head) {
        // IMPORTANT: Please reset any member data you declared, as
        // the same Solution instance will be reused for each test case.
        if(head == NULL) return NULL;
        ListNode* S = head;
        ListNode* F = head;
        
        while(F != NULL){
            if(F) F = F -> next;
            if(F) F = F -> next;
            if(S) S = S -> next;
            if(F != NULL && F == S){
                S = head;
                while(S != F){
                    S = S -> next;
                    F = F -> next;
                }
                return S;
            }
        }
        return NULL;
    }



2.5 sort list

使用归并排序

主要考察3个知识点,
知识点1:归并排序的整体思想
知识点2:找到一个链表的中间节点的方法
知识点3:合并两个已排好序的链表为一个新的有序链表

归并排序的基本思想是:找到链表的middle节点,然后递归对前半部分和后半部分分别进行归并排序,最后对两个以排好序的链表进行Merge。



三 排序

3.1 冒泡排序


       冒泡排序:对相邻的元素进行两两比较,顺序相反则进行交换,这样,每一趟会将最小或最大的元素到底端,最终达到完全有序

func bubbleSort(arr []int) {
	for i := 0; i < len(arr) - 1; i ++ {
		var flag = true
		for j := 0; j < len(arr) - i - 1; j++ {
			if arr[j + 1] < arr[j] {
				arr[j+1], arr[j] = arr[j], arr[j+1]
				flag = false
			}
		}

		if flag {
			break
		}
	}
}


3.2 插入排序

       直接插入排序基本思想是 每一步将一个待排序的记录,插入到前面已经排好序的有序序列中去,直到插完所有元素为止。

func insertionSort(arr []int) {
	for i := 0; i < len(arr); i++ {
		for j := i; j > 0; j-- {
			if arr[j] > a[j-1] {
				break
			}
			a[j], a[j-1] = a[j-1], a[j]
		}
	}
}


四 排列组合


4.1 全排列(递归)

将整组数中的所有的数分别与第一个数交换,这样就总是在处理后n-1个数的全排列。

package main

import "fmt"

func perm(list []int, k int) {
	if k == len(list) {
		for i := 0; i < len(list); i++ {
			fmt.Printf("%d ", list[i])
		}

		fmt.Printf("\n")
	} else {
		for i := k; i < len(list); i++ {
			list[i], list[k] = list[k], list[i]
			perm(list, k + 1)
			list[i], list[k] = list[k], list[i]
		}
	}
}

func main() {
	list := []int{1, 2, 3, 4}
	perm(list, 0)
}


4.2 全排列字典序(递归)

 
定理:对于一个排列 a1,a2,a3...an,如果 a(n)> a(n-1),那么 a1,a2,a3...a(n),a(n-1)是它后面的字典序,否则,也就是 a(n-1) > a(n),此时如果 a(n-2) < a(n-1),那么在 a(n-1)和 a(n)中选择比 a(n-2)大的较小的那个数,和 a(n-2)交换,显然,它也是原排列后面的字典序。更一般地,从 a(n)开始不断向前找,直到找到 a(m+1) > a(m)【如果 a(n) a(2) > ...a(n),是最大的字典序】,显然后面的序列满足 a(m+1)>a(m+2)>...a(n).找到 a(m+1)到 a(n)中比 a(m)大的最小的数,和 a(m)交换,并把交换后的 a(m+1)到 a(n)按照从小到大排序,前 m-1 项保持不变,得到的显然也是原排列后面的字典序,这个字典序便是紧挨着排列的后一个字典序。下面证明它是紧挨着的。1.如果还存在前 m-1 项和原排列相同并且也在原排列后面的字典序a1,a2,a3...bm,...,bm>原 am,假设它在我们构造的字典序前面,那么必有 bm < 交换后的 am,但这是不可能的,因为 am 是后面序列中大于原来 am 的最小的一个,而 bm 必然又是后面序列中的大于 am 的一个元素,产生了矛盾。2.如果还存在前前 m 项和原排列相同并且也在原排列后面的字典序,它不可能在我们构造的字典序前面,因为我们对后面的数进行了升序排列,不存在比 a(m+1)还小的数。3.如果还存在前 k 项(ka(k+1)[k+1 < m]

五 分治法

5.1 pow(xn)

     x^n = x^n/2 × x^n/2 × x^n%2

double myPow(double x, int n) {  
        if (n < 0)   
            return 1.0 / power(x, -n);  
        else   
            return power(x, n);  
        }  

    double power(double x, int n) {  
        if (n == 0)   
            return 1;  
        double v = power(x, n / 2);  
        if (n % 2 == 0)   
            return v * v;  
        else   
            return v * v * x;  
    } 



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值