C++数据结构和算法2 栈 双端/队列 冒泡选择插入归并快排 二三分查找 二叉树 二叉搜索树 贪婪 分治 动态规划

C++数据结构和算法2 栈 双端/队列 冒泡选择插入归并快排 二三分查找 二叉树 二叉搜索树 贪婪 分治 动态规划

博文末尾支持二维码赞赏哦 _

github

章3 Stack栈 和 队列Queue===========================

3.1 Stack栈 叠起来的 碗,放在一摞的书…都是栈的实例…

一种 插入 和 删除 操作只能在一端( 栈顶Top() )进行的 容器
是一种 后进先出(Last In First Out, LIFO) 的数据结构

// Stack.h =========================
template <typename T> // 模板类型
class Stack
{
private:   // 私有数据
 int m_count;// 熟练
 Node<T> * m_top; // 栈顶指针
 
 
public: // 公开
 Stack();       // 类构造函数
 bool IsEmpty();// 空检查
 T Top();       // 获取栈顶元素
 void Push(T val);// 栈顶插入元素
 void Pop();      // 弹出栈顶元素
};

// 获取栈顶元素=======
template <typename T>
T Stack<T>::Top()
{
 // Just return the value of m_top node
 return m_top->Value;
}

// 空检查===============
template <typename T>
bool Stack<T>::IsEmpty()
{
 // return TRUE if there are no items
 // otherwise, return FALSE
 return m_count <= 0;
}

// 栈顶插入元素===========
template <typename T>
void Stack<T>::Push(T val)
{
 // 创建一个新节点
 Node<T> * node = new Node<T>(val);
 // 新节点的 后继 指向 原 栈顶节点
 node->Next = m_top;
 // 新节点 重置为 新栈顶节点
 m_top = node;
 // 数量++
 m_count++;
}

// 弹出栈顶元素===========
template <typename T>
void Stack<T>::Pop()
{
 // 空栈直接返回
 if(IsEmpty())
      return;
 // 旧栈顶节点,待删除
 Node<T> * node = m_top;
 // 旧栈顶节点的后继 设置为 新栈顶节点
 m_top = m_top->Next;
 // 删除原 旧栈顶节点
 delete node;
 // 数量--
 m_count--;
}

// 使用===============
 // 初始化一个空栈 NULL
 Stack<int> stackInt = Stack<int>();
 // Store several numbers to the stack
 stackInt.Push(32);
 stackInt.Push(47);
 stackInt.Push(18);
 stackInt.Push(59);
 stackInt.Push(88);
 stackInt.Push(91);
 // 32->47->18->59->88->91|
 while(!stackInt.IsEmpty())
 {
 // 打印栈顶 节点 信息
 cout << stackInt.Top() << " ->";
 // 删除栈顶节点
 stackInt.Pop();
 }
 cout << "END" << endl;

栈典型应用 括号匹配 { ( ) [ { } ] }

{ ( ) [ { } ] }
1. 遇到左括号 入栈
2. 遇到右括号 检查 栈顶值 符号是否匹配,若匹配则对应左括号出栈
3. 最后 符号字符串读完,左括号栈 为空,则该符号串有效
bool IsValid (char expression[])
{
 int n = strlen(expression);
 Stack<char> stackChar = Stack<char>(); // 左符号 栈
 for (int i = 0; i < n; ++i)
 {
     // 遇到左符号 入栈
     if(expression[i] == '{')
     {
         stackChar.Push('{');
     }
     else if(expression[i] == '[')
     {
         stackChar.Push('[');
     }
     else if(expression[i] == '(')
     {
         stackChar.Push('(');
     }
     // 遇到右括号
     else if ( expression[i] == '}' ||
               expression[i] == ']' ||
               expression[i] == ')')
     {
         // 
         if(expression[i] == '}' &&
         (stackChar.IsEmpty() || stackChar.Top() != '{'))
             // 对应 右 } 应该匹配 { 否者 该字符串 非法
             return false;
         else if(expression[i] == ']' &&
         (stackChar.IsEmpty() || stackChar.Top() != '['))
             return false;
         else if(expression[i] == ')' &&
         (stackChar.IsEmpty() || stackChar.Top() != '('))
             return false;
         // 对应匹配的 左符号出栈======
         else
             stackChar.Pop();
     }
 }
 
 // 字符串遍历完后,左括号栈 为空,说明所有左右符号已经全部匹配,该字符串合法
 if (stackChar.IsEmpty())
     return true; //
 else
     return false;
}

3.2 队列Queue

一种只能从一端插入元素,从另一端删除元素的 容器
First In First Out(FIFO) 先入先出 队列
现实世界中的 排队,打印机队列...都是队列的实例
队首----->...---->首尾--->新节点

先入先出 队列

// https://github.com/Ewenwan/CPP-Data-Structures-and-Algorithms/blob/master/Chapter03/Queue/include/Queue.h
template <typename T>
class Queue
{
private:
    int m_count; // 节点数量
    Node<T> * m_front; // 前端节点(队首) 指针 删除元素入口
    Node<T> * m_back;  // 后端节点(队尾) 指针 插入元素入口

public:
    Queue();// 构造函数
    bool IsEmpty();// 为空检查
    T Front();     // 获取 队首 元素
    void Enqueue(T val); // 从队尾 插入元素
    void Dequeue();      // 从队首 删除元素
};

// 队尾插入元素
template <typename T>
void Queue<T>::Enqueue(T val)
{
    // 新建一个节点
    Node<T> * node = new Node<T>(val);

    if(m_count == 0)
    {
        // 为空时,新建的一个节点 即为队首也为队尾节点===
        node->Next = NULL;
        m_front = node;
        m_back = m_front;
    }
    else
    {
        // 队首----->...---->首尾--->新节点
        m_back->Next = node;
        // 新节点变成 新 队尾
        m_back = node;
    }

    // 数量++
    m_count++;
}

// 队首删除元素
template <typename T>
void Queue<T>::Dequeue()
{
    // 空
    if(m_count == 0)
        return;

    // 原队首节点
    Node<T> * node = m_front;
    // 原队首节点的后继 设置为 新队首
    m_front = m_front->Next;
    // 删除 原队首节点
    delete node;

    // 数量--
    m_count--;
}


// =================使用====
    // 创建新队列 NULL
    Queue<int> queueInt = Queue<int>();

    // 队尾插入元素=====
    queueInt.Enqueue(35);
    queueInt.Enqueue(91);
    queueInt.Enqueue(26);
    queueInt.Enqueue(78);
    queueInt.Enqueue(44);
    queueInt.Enqueue(12);

    // 从队首 依次打印 并删除 节点
    while(!queueInt.IsEmpty())
    {
        // 打印 队首 元素
        cout << queueInt.Front() << " - ";

        // 删除 节点
        queueInt.Dequeue();
    }
    cout << "END" << endl;

3.3 双端队列 Double Queue 双向节点构成

可以从两端进行插入和删除操作

双端队列 Double Queue

// https://github.com/Ewenwan/CPP-Data-Structures-and-Algorithms/blob/master/Chapter03/Deque/include/Deque.h
template <typename T>
class Deque
{
private:
    int m_count; // 数量
    DoublyNode<T> * m_front;// 队首节点指针  双向节点
    DoublyNode<T> * m_back; // 队尾节点指针

public:
    Deque();
    bool IsEmpty();
    T Front();
    T Back();
    void EnqueueFront(T val); // 队首插入元素 新节点后继指向原队首,原队首前继指向新节点,新节点作为新队首  
    void EnqueueBack(T val);  // 队尾插入元素 原队尾后继指向新节点  新节点前继指向原队尾,新节点作为新队尾
    void DequeueFront();      // 队首删除元素 保存旧队首,旧队首后继作为新队首,新队首前继指向NULL,删除旧队首
    void DequeueBack();       // 队尾删除元素 保存旧队尾,旧队尾前继作为新队尾,新队尾后继指向NULL,删除旧队尾
};

章4 排序算法 arranging 冒泡选择插入归并快排 ========

4.1 冒泡 Bubble

每次从剩余序列中找出最大值,最大值依次交换到最右端
改进1:其中如果未发送交换则序列已经有序,可提前结束
改进2:其中每次的变量交换找最大值 只需要遍历到 上次变量中最后一个交换的位置(该部分已经有序),跳过已经有序的部分
void BubbleSort(int arr[], int arrSize)
{
    // 是否有序标志
    bool isSwapped;
    int SwapIndex;
    // 未排序元素数量
    int unsortedElements = arrSize;
    do
    {
        // 是否发生交换的标志
        isSwapped = false;
        SwapIndex=0;
        // Iterate through the array's element
        for(int i = 0; i < unsortedElements - 1; ++i)
        {
            if (arr[i] > arr[i+1])
            {
                swap(arr[i], arr[i+1]);// 大的放在右边
                isSwapped = true;
                SwapIndex=i+1;// 已经有序的位置
            }
        }

        if(SwapIndex)
            unsortedElements=SwapIndex+1; // 跳过已经有序的部分===================改进=====
        else
            --unsortedElements;
    }
    // 如果未发生交换,则原序列已经 有序了===可提前结束排序过程
    while(isSwapped);
}

4.2 选择 Selection

冒泡改进,选择排序,找到最小的与指定位置交换,只交换一次

void SelectionSort(int arr[], int arrSize)
{
    // 最小元素索引
    int minIndex;

    // 总共遍历 n-1次
    for(int i = 0; i < arrSize - 1; ++i)
    {
        // 每次剩余序列中的最小值
        minIndex = i;
        // 在后面无序序列中寻找最小值
        for(int j = i + 1; j < arrSize; ++j)
        {
            // 更新最小元素 索引
            if (arr[j] < arr[minIndex])
                minIndex = j;
        }

        // 将最小元素放入有序序列的末尾
        swap(arr[i], arr[minIndex]);
    }
}

4.3 插入 Insert 排序,打牌

插入排序,类似打牌时,对拿到的牌一次插入到有序序列中的合适位置

void InsertionSort(int arr[], int arrSize)
{
    // 依次拿出后面无序序列中的元素,插入前面有序序列中的合适位置
    for(int i = 1; i < arrSize; ++i)
    {
        // 当前 需要插入的元素
        int refValue = arr[i];

        int j;
        // 0,...,i-1是已经拿到的牌,已经有序的序列
        for(j = i - 1; j >= 0; --j)
        {
            // 将当前元素 refValue 插入到 前面有序的序列中
            if(arr[j] > refValue)
                arr[j+1] = arr[j]; // 序列中大于带插入元素则依次后移 
            else
                break; // 找到 待插入元素 的 合适位置了
        }

        // 将 待插入元素插入合适位置
        // arr[j] 小于 refValue,则其应该插入 j + 1 位置
        arr[j + 1] = refValue;
    }
}

4.4 归并 Merge 先将序列依次二分,构建左右两个有序序列,最后合并在一起

https://github.com/Ewenwan/CPP-Data-Structures-and-Algorithms/blob/master/Chapter04/Merge_Sort/Merge_Sort.cpp

4.5快排 Quick

快排 Quick 左右指针,依次将比参考元素大的放在右边,反之放在左边,对子序列

// 按照参考元素,将大的元素放在一边,小的放在另一边,找到分区中枢 索引
int Partition(
    int arr[],
    int startIndex,
    int endIndex)
{
    // 子区间第一个元素 作为 参考元素
    int pivot = arr[startIndex];

    // 
    int middleIndex = startIndex;

    // Iterate through arr[1 ... n - 1]
    for (int i = startIndex + 1; i <= endIndex; ++i)
    {   
        // 单个指针 遍历,可以使用双指针版
        if (arr[i] < pivot)
        {
            ++middleIndex;// 左边小元素序列 尾id
            // 小元素放在左边
            swap(arr[i], arr[middleIndex]);
        }
    }

    // 参考元素放在 中枢的位置
    swap(arr[startIndex], arr[middleIndex]);

    // 中枢 索引
    return middleIndex;
}

void QuickSort(
    int arr[],
    int startIndex,
    int endIndex)
{
    // 
    if (startIndex < endIndex)
    {
        // 将数组分开,小的放左边,大的放右边,返回中枢索引
        int pivotIndex = Partition(arr, startIndex, endIndex);

        // 快排左边 arr[startIndex ... pivotIndex - 1]
        QuickSort(arr, startIndex, pivotIndex - 1);

        // 快排右边  arr[pivotIndex + 1 ... endIndex]
        QuickSort(arr, pivotIndex + 1, endIndex);
    }
}

4.6 计数 Counting 先统计数据 生成 直方图分布 按照直方图生成有序数组

生成 直方图分布 按照直方图生成有序数组

void CountingSort(int arr[], int arrSize)
{
    // 生成 数组数据 的 直方图分布
    
    // 需要假设数据范围,可以先找到 数据的最大值最小值
    int counterSize = 10;
    int * counterArray = new int [counterSize];
    // 直方图统计
    for(int i = 0; i < arrSize; ++i)// 每个数组元素 划分到 对应的 直方图bin中
    {
        ++counterArray[arr[i]]; // 对应元素arr[i] 占据的 直方图bin 计数+1 
    }

    // 按照数据直方图分布生成 有序数组====
    int arrCounter = 0;// 数组index
    for(int i = 0; i < counterSize; ++i)// 所有直方图bin
    {
        while(counterArray[i] > 0)// 该bin 还有计数,原数组中有该bin的值(可能不止一个)
        {
            // 从小到大的直方图 bin 依次放入 有序数组
            arr[arrCounter++] = i;
            // 该bin 计数-1
            --counterArray[i];
        }
    }
}

4.7 基数 Radix 先按最低位值 0~9直方图分布,再按次低位数字直方图分布,…,

https://github.com/Ewenwan/CPP-Data-Structures-and-Algorithms/blob/master/Chapter04/Radix_Sort/Radix_Sort.cpp

章5 查找算法 Searching 线性二分====================

5.1 线性 Linear search

按索引一次对比,线性查找

int LinearSearch(
    int arr[],
    int startIndex,
    int endIndex,
    int val)
{
    // 按索引一次对比
    for(int i = startIndex; i < endIndex; ++i)
    {
        if(arr[i] == val) // 找到
        {
            return i;// 返回对应的 位置索引
        }
    }
    return -1; // 未找到 返回-1
}

5.2 二分 Binary search

对于有序序列,使用二分搜索,不断缩小搜索空间

int BinarySearch(
    int arr[],
    int startIndex,
    int endIndex,
    int val)
{
    // 对于有序序列,不断缩小搜索空间
    if(startIndex <= endIndex)
    {
        // 子序列 中间位置
        int middleIndex = startIndex + (endIndex - startIndex) / 2;

        // 相等的情况,更少见,适当放在最后一个 判断分支
        if(arr[middleIndex] == val)
        {
            return middleIndex;
        }
        // 中间值大于要找的值,原序列为升序排列,则在左边找 arr[startIndex ... middleIndex - 1]
        else if(arr[middleIndex] > val)
        {
            return BinarySearch(arr, startIndex, middleIndex - 1, val);
        }
        //  在右边找 arr[middleIndex + 1 ... endIndex]
        else
        {
            return BinarySearch(arr, middleIndex + 1, endIndex, val);
        }
    }

    // 没找到-1
    return -1;
}

5.3 三分 Ternary search

有序区间三分,依次检查,迭代三个区间中的不同区间

int TernarySearch(
    int arr[],
    int startIndex,
    int endIndex,
    int val)
{
    // 不断搜小搜索空间
    if(startIndex <= endIndex)
    {
        // 三分左边第一个点
        int middleLeftIndex = startIndex + (endIndex - startIndex) / 3;

        // 三分左边第二个点
        int middleRightIndex =
            middleLeftIndex + (endIndex - startIndex) / 3;

        // 检查 第一个点值
        if(arr[middleLeftIndex] == val)
        {
            return middleLeftIndex;
        }
        // 检查第二个点值
        else if(arr[middleRightIndex] == val)
        {
            return middleRightIndex;
        }
        // 迭代三分中的第一个区间 arr[startIndex ... middleLeftIndex - 1]
        else if(arr[middleLeftIndex] > val)
        {
            return TernarySearch(
                arr,
                startIndex,
                middleLeftIndex - 1,
                val);
        }
        // 迭代三分中的第三个区间arr[middleRightIndex + 1 ... endIndex]
        else if(arr[middleRightIndex] < val)
        {
            return TernarySearch(
                arr,
                middleRightIndex + 1,
                endIndex,
                val);
        }
        // 迭代三分中的第二个区间 arr[middleLeftIndex + 1 ... middleRightIndex - 1]
        else
        {
            return TernarySearch(
                arr,
                middleLeftIndex + 1,
                middleRightIndex - 1,
                val);
        }
    }
    // 没找到-1
    return -1;
}

5.4 插补 Interpolation Search 有序序列的 加权取中点 二分查找

加权二分查找

int InterpolationSearch(
    int arr[],
    int lowIndex,
    int highIndex,
    int val)
{
    if(lowIndex <= highIndex)
    {
        // 加权取中点 例如 30个元素,最小值5,最大值100,需要查找40
        // 则按比例 40出现的位置为 (40-5)*30/(100-5)
        int middleIndex =
            lowIndex + (
                (val - arr[lowIndex]) * (highIndex - lowIndex) /
                (arr[highIndex] - arr[lowIndex]));

        // 比较是否为 寻找的元素
        if(arr[middleIndex] == val)
        {
            return middleIndex;
        }
        // 递归左边 arr[lowIndex ... middleIndex - 1]
        else if(arr[middleIndex] > val)
        {
            return InterpolationSearch(arr, lowIndex, middleIndex - 1, val);
        }
        // 递归右边 arr[middleIndex + 1 ... highIndex]
        else
        {
            return InterpolationSearch(arr, middleIndex + 1, highIndex, val);
        }
    }

    // 未找到 -1
    return -1;
}

5.5 跳跃 Jump Search

先按 线性间隔点 找到目标值出现的子序列,在确定的子序列中线性查找

https://github.com/Ewenwan/CPP-Data-Structures-and-Algorithms/blob/master/Chapter05/Jump_Search/Jump_Search.cpp

5.6 指数 Exponential search

按 指数分布间隔点 找到目标值出现的子序列,在确定的子序列中二分查找

5.7 子表 Sublist search

https://github.com/Ewenwan/CPP-Data-Structures-and-Algorithms/blob/master/Chapter05/Sublist_Search/Sublist_Search.cpp

章6 String 字符数据算法===========================

// 字符数组 构造
char name[] = "James"; # 默认会加 '\0'结束符
char name[] = {'J', 'a', 'm', 'e', 's', '\0'};
char name[6] = "James";
char name[6] = {'J', 'a', 'm', 'e', 's', '\0'}

// std::string 字符串类 STL容器
getline();
push_back();
pop_back();
size();
// 迭代器
begin();  // 正向 
end();
rbegin(); // 反向
rend();

变位词 anagram 重构词

triangle(三角形)就有integral(构成整体所必要的)这个变位词
Silent(不要吵)和Listen(听我说)也是
bool IsAnagram(
    string str1,
    string str2)
{
    // 转变成 大写
    transform(
        str1.begin(),
        str1.end(),
        str1.begin(),
        ::toupper);
    transform(
        str2.begin(),
        str2.end(),
        str2.begin(),
        ::toupper);

    // 删除 空格
    str1.erase(
        remove(
            str1.begin(),
            str1.end(),
            ' '),
            str1.end());
    str2.erase(
        remove(
            str2.begin(),
            str2.end(),
            ' '),
            str2.end());

    // A-Z 排序
    sort(str1.begin(), str1.end());
    sort(str2.begin(), str2.end());
    // 或者使用 26个字母 直方图统计,比较字符串的 直方图
    
    // 排序后如果相同,则为 变位词
    return str1 == str2;
}

回文 palindrome 顺读和倒读都一样的词语

bool IsPalindrome(
    string str)
{
    // 变大写
    transform(
        str.begin(),
        str.end(),
        str.begin(),
        ::toupper);

    // 去除空格
    str.erase(
        remove(
            str.begin(),
            str.end(),
            ' '),
            str.end());

    // 首尾指针
    int left = 0;
    int right = str.length() - 1;

    // 首尾指针 向中间遍历 依次比较 对应位置 的 字符 
    while(right > left)
    {
        if(str[left++] != str[right--])
        {
            return false;// 对应位置由不同的,则不是回文
        }
    }
    // 对应位置 字符全部相同,则为回文
    return true;
}

十进制数 转 二进制字符串 不断除2取余…

章7 树结构算法 Tree==============================

7.1 二叉树(Binary Tree)

参考

/*   A simple binary tree
 *        A ---------> A is root node---根节点
 *       / \
 *      /   \
 *     B     C
 *    /     / \
 *   /     /   \
 *   D     E    F ---> leaves: D, E, F---叶子leaf节点
 *
 *       (1)      ---> Height: 3
 *    B节点只有一个子节点D,这是非完全二叉树
 *    常用来存储具有等级层级关系的数据,例如计算机的文件系统数据,以及家族谱等
 * */
二叉树(Binary Tree)是最简单的树形数据结构,然而却十分精妙。
其衍生出各种算法,以致于占据了数据结构的半壁江山。
STL中大名顶顶的关联容器——集合(set)、映射(map)便是使用二叉树实现。
二叉树的根节点入度为0,叶子节点出度为0。

与楼房一样,一般会对二叉树分层,并且通常将根节点视为第一层。
接下来B与C同属第二层,D, E, F同属第三层。注意,并不是所有的叶子都在同一层。
通常将二叉树节点的最高层数作为其树的高度,上例中二叉树高度为3。
显然,一个二叉树的节点总数必然小于2的树高幂,
转化成公式表示为:N<2^H,其中N为节点总数,H为二叉树高度;对于第k层,最多有2^(k-1)个节点。
#include 

using namespace std;

class TreeNode  // 树节点
{
public:
    int Key;// 节点存储的信息数据
    TreeNode * Left;  // 左子节点 指针
    TreeNode * Right; // 右子节点 指针
};

// 创建一个新节点,传入一个节点信息数据
TreeNode * NewTreeNode(int key)
{
    // 创建一个新节点Create a new node
    TreeNode * node = new TreeNode;
    // 更新节点信息数据
    node->Key = key;
    // 左右子节点指针初始化为 NULL
    node->Left = NULL;
    node->Right = NULL;
    return node;
}

// 按顺序打印二叉树,递归实现 遍历 二叉树,递归遍历 左/右子树
void PrintTreeInOrder(TreeNode * node)
{
	if(node == NULL) return;// 递归停止
	PrintTreeInOrder(node->Left); // 打印左子树
	cout << node->Key << " ";     // 打印当前节点信息数据
	PrintTreeInOrder(node->Right);// 打印右子树
}

// 递归打印 二叉树,带有标记信息
void Print (TreeNode * x, int & id)
{
    if (!x) return;// 节点指针不为空,递归结束情况
    Print (x->Left,id);// 递归打印左子树
    id++;// 标记 id信息,在树中的 排行,家族谱中的地位
    cout << id << ' ' << x->Key << endl;// 打印节点信息
    Print (x->Right,id);// 递归打印右子树
}

int main()
{
    cout << "Binary Tree" << endl;

    // 创建一个树根节点 Creating root element
    TreeNode * root = NewTreeNode(1);
    /*
        为根节点添加 左/右 两个孩子节点。Add children to root element
          1
         / \
        2   3
    */
    root->Left = NewTreeNode(2);
    root->Right = NewTreeNode(3);

    /*
        为节点2添加两个孩子节点 Add children to element 2
             1
           /   \
          2     3
         / \
        4   5
    */
    root->Left->Left = NewTreeNode(4);
    root->Left->Right = NewTreeNode(5);

    /*
        为节点3添加两个节点 Add children to element 3
             1
           /   \
          2     3
         / \   / \
        4   5 6   7
    */
    root->Right->Left = NewTreeNode(6);
    root->Right->Right = NewTreeNode(7);

    int id = 0;
    Print(root, id);// 按照地位信息,打印树的各个节点信息数据

    return 0;
}

7.2 二叉搜索树(Binary Search Tree) 二叉搜索树,左子树<=父节点<=右子树

二叉搜索树,左子树<=父节点<=右子树,使用递归结构来实现插入搜索删除等行为

/*
// BST任何一颗子树上的三个节点left, parent, right. 满足条件left.key

#ifndef BSTNODE_H
#define BSTNODE_H

#include 

class BSTNode  // 二叉搜索树节点
{
public:
    int Key;
    BSTNode * Left; // 左子节点 指针
    BSTNode * Right;// 右子节点 指针
    BSTNode * Parent; // 比普通的二叉树多一个父节点指针
                      // 因为要 通过左子节点 访问 父节点,方便遍历 二叉搜索树
    int Height;//当前子树的高度??
};

class BST // 二叉搜索树
{
private:
    BSTNode * root; // 树根节点

protected: // 保护类型,可继承==
    BSTNode * Insert(BSTNode * node, int key);
      // 树中插入一个节点,若为第一次,则设置为根节点,之后根据大小 递归放入左/右子树中的合适位置
    void PrintTreeInOrder(BSTNode * node); // 按 序列 递归打印 二叉搜索树 各个节点信息 
    BSTNode * Search(BSTNode * node, int key); // 搜索一个节点,节点值比给定值大,在左子树递归查找,反之在右子树递归查找
    int FindMin(BSTNode * node);// 最小值,一个BST的最左叶子节点的key值就是BST所有key值中最小的,不停递归左子树,直到无左子树时。
    int FindMax(BSTNode * node);// 最大值,一个BST的最右叶子节点的key值就是BST所有key值中最大的,不停递归右子树,直到无右子树时。
    int Successor(BSTNode * node);// x的SUCCESSOR满足x.key<=x.SUCCESSOR.key,并且x.SUCCESSOR.key是距离x.key最近的值,
                                  // 即x.SUCCESSOR.key是x.key的最小上限(minimum ceiling)
                                  // 在右子树/左父亲 中 递归 寻找最小的
    int Predecessor(BSTNode * node);// 最大下限==
                                    // 在左子树/右父亲 递归 寻找最大的
    
    BSTNode * Remove(BSTNode * node, int v); // 删除节点

public:
    BST();
    
    // 外部可访问==============
    void Insert(int key);
    void PrintTreeInOrder();
    bool Search(int key);
    int FindMin();
    int FindMax();
    int Successor(int key);
    int Predecessor(int key);
    void Remove(int v);
};

#endif // BSTNODE_H

// 完整实现
https://github.com/Ewenwan/CPP-Data-Structures-and-Algorithms/blob/master/Chapter07/Binary_Search_Tree/include/BSTNode.cpp

// 部分实现===
// 打印树=
void BST::PrintTreeInOrder(BSTNode * node)
{
    // Stop printing if no node found
    if(node == NULL)
        return;

    //打印左子树
    PrintTreeInOrder(node->Left);

    // 打印当前节点值
    std::cout << node->Key << " ";

    // 打印右子树
    PrintTreeInOrder(node->Right);
}
// 搜索一个节点,节点值比给定值大,在左子树递归查找,反之在右子树递归查找
BSTNode * BST::Search(BSTNode * node, int key)
{
    // 树非空,递归结束条件
    if (node == NULL)
        return NULL;
    // 节点值 正好等于 查找值
    else if(node->Key == key)
        return node;
    // 给定值大,在右子树中查找
    else if(node->Key < key)
        return Search(node->Right, key);
    // 给定值小在 左子树中查找
    else
        return Search(node->Left, key);
}
// 一个BST的最左叶子节点的key值就是BST所有key值中最小的。
int BST::FindMin(BSTNode * node)
{
    if(node == NULL)
        return -1;
    // 无左子节点了,该节点就是最小值
    else if(node->Left == NULL)
        return node->Key;
    // 在 左子树中递归左子树
    else
        return FindMin(node->Left);
}

int BST::FindMin()
{
    return FindMin(root);
}

// 一个BST的最右叶子节点的key值就是BST所有key值中最大的。
int BST::FindMax(BSTNode * node)
{
    if(node == NULL)
        return -1;
    // 无右子节点了,该节点就是最大值
    else if(node->Right == NULL)
        return node->Key;
    else
    // 在 右子树中递归右子树
        return FindMax(node->Right);
}

7.3 平衡二叉搜索树 balanced BST (AVL) Adelson-Velskii 和 Landis 在 1962 发明

平衡二叉树是对二叉查找的一种改进,对于二叉查找树的一个明显的缺点就是,
树的结构仍旧具有极大的变动性,最坏的情况下就是一棵单支二叉树,丢失了二叉查找树一些原有的优点。
平衡二叉树定义(AVL):
        它或者是一棵空树,或者是具有一下性质的二叉查找树--
	它的结点左子树和右子树的深度之差不超过1,而且该结点的左子树和右子树都是一棵平衡二叉树。
 平衡因子:结点左子树的深度-结点右子树的深度。

https://github.com/Ewenwan/CPP-Data-Structures-and-Algorithms/blob/master/Chapter07/Binary_Search_Tree/src/AVLNode.cpp

7.4 二叉堆(Binary heap)

参考

二叉堆(binary heap)是一种通常用于实现优先队列的数据结构。
二叉堆是一颗除底层外被完全填满的二叉树,对于底层上的元素满足从左到右填入的特性。
基于二叉堆的这个特性,我们可以用一个数组在表示这种数据结构,不需要使用链。

https://github.com/Ewenwan/CPP-Data-Structures-and-Algorithms/blob/master/Chapter07/Binary_Heap/src/BinaryHeap.cpp  

章8 哈希算法 Hash===============================

8.1 hash tables

https://github.com/Ewenwan/CPP-Data-Structures-and-Algorithms/blob/master/Chapter08/Hash_Table_SC/src/HashTable.cpp

HashTable::HashTable()
{
    for (int i = 0; i < TABLE_SIZE; ++i)
      tableList[i].clear();
}

int HashTable::HashFunction(int key)
{
    return key % TABLE_SIZE;
}

章9 常用算法Real Life 贪婪&分治&动态规划&暴力&随机&回溯====

9.1 贪婪 Greedy algorithms

coin-changing 找零

// 找零问题,反向遍历可找零币值列表,尽量使用 大币值 找零,剩余找零小于 最小币值 时 结束
#include 
#include 

using namespace std;

void MinimalChangeCoin(double changingNominal)
{
    // 所有美元币值 数组
    double denom[] =
        {0.01, 0.05, 0.10, 0.25, 1, 2, 5, 10, 20, 50, 100};
    // 数组元素数量
    int totalDenom = sizeof(denom) / sizeof(denom[0]);

    // 初始化一个结果
    vector<double> result;

    // 反向遍历可找零币值列表
    for (int i = totalDenom - 1; i >= 0; --i)
    {
        // 尽量使用 大币值 找零
        while (changingNominal >= denom[i])
        {
           changingNominal -= denom[i];// 使用 denom[i]币
           result.push_back(denom[i]); // 记录使用
        }
        // 剩余找零小于 最小币值 则 结束
        if (changingNominal < denom[0])
            break;
    }

    // 打印找零钱币列表
    for (int i = 0; i < result.size(); ++i)
        cout << result[i] << " ";
    cout << endl;
}

int main()
{
    cout << "Coin Change Problem" << endl;

    // 总找零钱数
    float change = 17.61;

    // Getting the minimal
    cout << "Minimal number of change for ";
    cout << change << " is " << endl;
    MinimalChangeCoin(change);

    return 0;
}

Huffman coding 霍夫曼编码

https://github.com/Ewenwan/CPP-Data-Structures-and-Algorithms/blob/master/Chapter09/Huffman_Coding/main.cpp

霍夫曼编码(Huffman Coding)是一种编码方法,霍夫曼编码是可变字长编码(VLC)的一种。

霍夫曼编码使用变长编码表对源符号(如文件中的一个字母)进行编码,其中变长编码表是通过一种评估来源符号出现机率的方法得到的,出现机率高的字母使用较短的编码,反之出现机率低的则使用较长的编码,这便使编码之后的字符串的平均长度、期望值降低,从而达到无损压缩数据的目的。

霍夫曼编码的具体步骤如下:

1)将信源符号的概率按减小的顺序排队。

2)把两个最小的概率相加,并继续这一步骤,始终将较高的概率分支放在右边,直到最后变成概率1。

3)画出由概率1处到每个信源符号的路径,顺序记下沿路径的0和1,所得就是该符号的霍夫曼码字。   

4)将每对组合的左边一个指定为0,右边一个指定为1(或相反)。

例:现有一个由5个不同符号组成的30个符号的字符串:

BABACAC ADADABB CBABEBE DDABEEEBB

1首先计算出每个字符出现的次数(概率):
	B   10次
	A   8
	C   3
	D   4
	E   5
把出现次数(概率)最小的两个相加,并作为左右子树,重复此过程,直到概率值为1
左小右大
 1.   B:10  A:8  C:3  D:4  E:5
 2.   B:10  A:8     7     E:5
                  /  \
                C:3  D:4
 3.   B:10  A:8     12 	       
	           /  \
		  E:5  7     
                      /  \
                     C:3  D:4
	       
 4.    12    18        
            /   \
          A:8   B:10   
   
 5.       30 
       /      \
     12        18        
    /   \      /   \
   E:5   7    A:8  B:10
        /   \
       C:3   D:4
		  
将每个二叉树的左边指定为0,右边指定为1

 6.      30 
     0/     1\
     12        18        
    0/   1\   0/  1\
   E:5   7    A:8  B:10
       0/  1\
      C:3  D:4

沿二叉树顶部到每个字符路径,获得每个符号的编码
                  编码
	B   1011
	A   8     10
	C   3     010
	D   4     011
	E   5     00
	
我们可以看到出现次数(概率)越多的会越在上层,编码也越短,出现频率越少的就越在下层,编码也越长。
当我们编码的时候,我们是按“bit”来编码的,解码也是通过bit来完成,
如果我们有这样的bitset “10111101100″ 那么其解码后就是 “ABBDE”。
所以,我们需要通过这个二叉树建立我们Huffman编码和解码的字典表。

这里需要注意的是,Huffman编码使得每一个字符的编码都与另一个字符编码的前一部分不同,
不会出现像’A’:00,  ’B’:001,这样的情况,解码也不会出现冲突。

霍夫曼编码的局限性

利用霍夫曼编码,每个符号的编码长度只能为整数,所以如果源符号集的概率分布不是2负n次方的形式,则无法达到熵极限;
输入符号数受限于可实现的码表尺寸;译码复杂;
需要实现知道输入符号集的概率分布;没有错误保护功能。


9.2 分治 Divide and conquer algorithms

	       问题
	      divide 
	     分解成小问题
	  /              \
       子问题1          子问题2-问题求解 |  conquer    |
       子问题1答案      子问题2答案
           \           /
	     \       /
           合并结果 combine
	     问题结果
	     
实例:
      A. 有序列表的 二分搜索,
             待查元素 和 中间元素比较,
             1.若相等,返回中间元素索引
	     2.待查元素 < 中间元素,则递归遍历左区间
	     3.待查元素 > 中间元素,则递归遍历右区间
      B. 快排
             1.选择一个 比较中枢元素
	     2.比 比较元素小的 移动到左边,反之移动到右边
	     3.比较元素 放入 分割中枢
	     4.递归左区间
	     5.递归右区间
	     
     C.  归并排序
             1. 将数组一次分成两个区间
	     2. 对每个子区间进行排序
	     3. 合并每个子区间
	     
      D. 选择问题   selection problems
      E. 矩阵乘法计算问题 matrix multiplication
                 矩阵分块 进行 值矩阵乘法
          卷积运算,矩阵乘法 快速矩阵乘法 
	https://github.com/Ewenwan/MVision/blob/master/CNN/HighPerformanceComputing/%E5%BF%AB%E9%80%9F%E7%9F%A9%E9%98%B5%E4%B9%98%E6%B3%95.md
	

9.3 动态规划 Dynamic programming

斐波那契数列 动态规划实现 更新记录前两项,当前项 = 前一项 + 前前一项

// 斐波那契数列  递归实现
long long fib(int n)
{
    if (n <= 1)
        return 1; // 递归结束条件
    
    else
        // fn = fn-1 + fn-2 前两项之和
        return fib(n - 1) + fib(n - 2);
}

// 斐波那契数列 动态规划实现 更新记录前两项,当前项 = 前一项 + 前前一项
long long fib2(int n)
{
    if(n <= 1)
        return 1;

    long long last = 1;      // 前一项
    long long nextToLast = 1;// 前前一项
    long long answer = 1;    // 当前项

    for(int i = 2; i <=n; ++i)
    {
        answer = last + nextToLast;// 当前项 = 前一项 + 前前一项
        
        // 更新 记录前两项
        nextToLast = last;// 前前一项
        last = answer;// 前一项
    }

    return answer;
}

动态规划解决 找零问题 = 总钱数i 使用S[j]找零 + 总钱数i 不使用S[j]找零

// S[] 可找零钱币列表 m币种数量 n待找零钱数
int count(int S[], int m, int n)
{
    int x, y;

    // 建立一个二维表 Base саѕе (n = 0)
    int table[n + 1][m];

    // Fіll thе еntеrіеѕ for 0 vаluе саѕе
    // (n = 0)
    for (int i = 0; i < m; ++i)
        table[0][i] = 1; // 

    // Fill rеѕt оf the table еntrіеѕ іn bоttоm
    // up mаnnеr
    for (int i = 1; i < n + 1; ++i) // 需要找零的 钱 数,需要的找零 币值范围
    {
        for (int j = 0; j < m; ++j)// 每种币值
        {
            // 总钱数i 使用S[j]找零 solutions соunt іnсludіng S[j]
               // 结果和 总钱数i-S[j] 找零 一样
            x = (i - S[j] >= 0) ?
                table[i - S[j]][j] :
                0;

            // 总钱数i 不使用S[j]找零 ѕоlutіоnѕ соunt excluding S[j]
            y = (j >= 1) ? table[i][j-1] : 0;

            // 两种情况之和
            table[i][j] = x + y;
        }
    }

    return table[n][m-1];
}

9.4 暴力 Brute-force algorithms



9.5 随机 Randomized algorithms

蒙特卡罗(Monte Carlo)方法,也称为计算机随机模拟方法,是一种基于"随机数"的计算方法。
早在17世纪,人们就知道用事件发生的"频率"来决定事件的"概率"19世纪人们用投针试验的方法来决定圆周率π。
本世纪40年代电子计算机的出现,特别是近年来高速电子计算机的出现,
使得用数学方法在计算机上大量、快速地模拟这样的试验成为可能。 


CAPTCHA 的目的是区分计算机和人类的一种程序算法,是一种区分用户是计算机和人的计算程序,
这种程序必须能生成并评价人类能很容易通过但计算机却通不过的测试。



9.6 回溯 Backtracking algorithms

回溯算法(Backtracking)在很多场景中会使用,如N皇后,数迷,集合等,其是暴力求解的一种优化。
https://blog.csdn.net/u011319202/article/details/73457646

回溯算法也叫试探法,它是一种系统地搜索问题的解的方法。
回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。

回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,
当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。
回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。
但当探索到某一步时,发现原先选择并不优或达不到目标,
就退回一步重新选择,这种走不通就退回再走的技术为回溯法。
适用于求解组合数较大的问题。

对于回溯问题,总结出一个递归函数模板,包括以下三点

  1. 递归函数的开头写好跳出条件,满足条件才将当前结果加入总结果中
  2.已经拿过的数不再拿 if(s.contains(num)){continue;}
  3. 遍历过当前节点后,为了回溯到上一步,要去掉已经加入到结果list中的当前节点。
针对不同的题目采用不同的跳出条件或判断条件。

回溯法用于搜索解,dp找最优解。

用爬山来比喻回溯,好比从山脚下找一条爬上山顶的路,起初有好几条道可走,
当选择一条道走到某处时,又有几条岔道可供选择,只能选择其中一条道往前走,
若能这样子顺利爬上山顶则罢了,否则走到一条绝路上时,只好返回到最近的一个路口,
重新选择另一条没走过的道往前走。如果该路口的所有路都走不通,只得从该路口继续回返。
照此规则走下去,要么找到一条到达山顶的路,要么最终试过所有可能的道,无法到达山顶。 

回溯是一种穷举,但与brute force有一些区别,回溯带了两点脑子的,并不多,brute force一点也没带。 
第一点脑子是回溯知道回头;相反如果是brute force,发现走不通立刻跳下山摔死,换第二条命从头换一条路走。 
第二点脑子是回溯知道剪枝;如果有一条岔路上放了一坨屎,那这条路我们不走,就可以少走很多不必要走的路。

还有一些爱混淆的概念:递归,回溯,DFS。 
1. 回溯是一种找路方法,搜索的时候走不通就回头换路接着走,直到走通了或者发现此山根本不通。 
2. DFS是一种开路策略,就是一条道先走到头,再往回走一步换一条路走到头,这也是回溯用到的策略。
   在树和图上回溯时人们叫它DFS。 
3. 递归是一种行为,回溯和递归如出一辙,都是一言不合就回到来时的路,所以一般回溯用递归实现;
   当然也可以不用,用栈。 
以下以回溯统称,因为这个词听上去很文雅。

这里面有一个通用的算法

ALGORITHM try(v1,...,vi)  // 这里的V1.....V2携带的参数说明 “可能解”  
   // 入口处验证是否是全局解,如果是,直接返回。 
   // 实际编程中也需要查看是否是无效解,如果是,也是直接返回
   IF (v1,...,vi) is a solution THEN RETURN (v1,...,vi)  
   FOR each v DO  // 对于每一个可能的解,进行查看
      // 下面的含义是形成一个可能解 进行递归
      IF (v1,...,vi,v) is acceptable vector  THEN 
        sol = try(v1,...,vi,v) 
        IF sol != () THEN RETURN sol 
        // 这个地方其实需要增加“回溯” 处理,实际编程中通常是函数参数的变化
      END 
   END 
   RETURN () 

C++数据结构和算法2 栈 双端/队列 冒泡选择插入归并快排 二三分查找 二叉树 二叉搜索树 贪婪 分治 动态规划_第1张图片

你可能感兴趣的:(算法与数据结构,算法与数据结构,排序搜索,栈队列,二叉树,贪婪分治动归)