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 nodereturn m_top->Value;
}// 空检查===============
template <typename T>
bool Stack<T>::IsEmpty()
{// return TRUE if there are no items// otherwise, return FALSEreturn 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--;
}// 使用===============// 初始化一个空栈 NULLStack<int> stackInt = Stack<int>();// Store several numbers to the stackstackInt.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;// 对应匹配的 左符号出栈======elsestackChar.Pop();}}// 字符串遍历完后,左括号栈 为空,说明所有左右符号已经全部匹配,该字符串合法if (stackChar.IsEmpty())return true; //elsereturn 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--;
}// =================使用====// 创建新队列 NULLQueue<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 elementfor(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]; // 序列中大于带插入元素则依次后移 elsebreak; // 找到 待插入元素 的 合适位置了}// 将 待插入元素插入合适位置// 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;// 数组indexfor(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);}}// 没找到-1return -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);}}// 没找到-1return -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);}}// 未找到 -1return -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 <iostream>using namespace std;class TreeNode  // 树节点
{public:int Key;// 节点存储的信息数据TreeNode * Left;  // 左子节点 指针TreeNode * Right; // 右子节点 指针
};// 创建一个新节点,传入一个节点信息数据
TreeNode * NewTreeNode(int key)
{// 创建一个新节点Create a new nodeTreeNode * node = new TreeNode;// 更新节点信息数据node->Key = key;// 左右子节点指针初始化为 NULLnode->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 elementTreeNode * root = NewTreeNode(1);/*为根节点添加 左/右 两个孩子节点。Add children to root element1/ \2   3*/root->Left = NewTreeNode(2);root->Right = NewTreeNode(3);/*为节点2添加两个孩子节点 Add children to element 21/   \2     3/ \4   5*/root->Left->Left = NewTreeNode(4);root->Left->Right = NewTreeNode(5);/*为节点3添加两个节点 Add children to element 31/   \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<parent.key<=right.key,
// 一颗典型的BST如下图所示:
//    /  6   \
//  / 5 \    7 \
// 2     5      8
// 观察之后不难发现如果对BST进行PREORDER walk(先序遍历),得到:2, 5, 5, 6, 7, 8 刚好是升序排列。
// 所谓PREORDER walk,就是要访问以ROOT为根的树,先要访问ROOT.left, 然后访问ROOT, 最后访问ROOT.right。
之所以称为二叉搜索树,是因为这种二叉树能大幅度提高搜索效率。
如果一个二叉树满足:对于任意一个节点,其值不小于左子树的任何节点,
且不大于右子树的任何节点(反之亦可),则为二叉搜索树。如果按照中序遍历,其遍历结果是一个有序序列。
因此,二叉搜索树又称为二叉排序树。不同于最大堆(或最小堆),
其只要求当前节点与当前节点的左右子节点满足一定关系。
下面以非降序二叉搜索树为例。*  A simple binary search tree*           6                  6*          / \                / \*         /   \              /   \*        3     8            3     8*       /     / \          /     / \*      /     /   \        /     /   \*     2     7     9      2     4*    9**       (A) BST             (B) 非 BST, 因为根节点6大于右子树中的节点4。*/#ifndef BSTNODE_H
#define BSTNODE_H#include <iostream>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 foundif(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);// 给定值小在 左子树中查找elsereturn 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;// 在 左子树中递归左子树elsereturn 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.cppHashTable::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 <iostream>
#include <vector>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 minimalcout << "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 DDABEEEBB1首先计算出每个字符出现的次数(概率):B   10次A   8C   3D   4E   5
把出现次数(概率)最小的两个相加,并作为左右子树,重复此过程,直到概率值为1
左小右大1.   B:10  A:8  C:3  D:4  E:52.   B:10  A:8     7     E:5/  \C:3  D:43.   B:10  A:8     12         /  \E:5  7     /  \C:3  D:44.    12    18        /   \A:8   B:10   5.       30 /      \12        18        /   \      /   \E:5   7    A:8  B:10/   \C:3   D:4将每个二叉树的左边指定为0,右边指定为16.      30 0/     1\12        18        0/   1\   0/  1\E:5   7    A:8  B:100/  1\C:3  D:4沿二叉树顶部到每个字符路径,获得每个符号的编码编码B   10次  11A   8     10C   3     010D   4     011E   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 problemsE. 矩阵乘法计算问题 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еrfor (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. 算法学习-单调双端队列

    文章目录 基础知识 算法模板 相关题目 239.滑动窗口最大值 1438.绝对差不超过限制的最长连续子数组 862.和至少为K的最短子数组 1425.带限制的子序列和 1499.满足不等式的最大值 2 ...

  2. HDU 4286 Data Handler [栈,双端队列]

    这题比较容易想到的做法是splay,但是splay写起来比较麻烦而且每次操作都有LogN的复杂度,双向链表也是可以实现的,但实践起来比较麻烦,尤其是翻转操作... 可以发现每次L或者R都是移动一位的, ...

  3. 图论:dij算法优化:双端队列及详细证明

    dij原来的写法请移步这里 首先,让我们举一个洛谷中的情境 这题中,我们可以二分mid答案,小于等于mid的边权是0,大于的是1,再计算最短路是否<=k: 那么在这样边权只有0和1的时候,dij ...

  4. 栈和队列之LinekedList(双端队列)

    介绍: ( deque,全名double-ended queue)是一种具有队列和栈的性质的数据结构.双端队列中的元素可以从两端弹出,其限定插入和删除操作在表的两端进行. 双端队列是限定插入和删除操作 ...

  5. C++中STL容器之双端队列——dequeue

    1.双端队列介绍 双端队列(dequeue) 与vector很类似,采用线性表顺序存储结构,且支持随机访问,即可以直接用下标来访问元素.但与vector有区别: deque采用分块的线性存储结构来存储 ...

  6. python数据结构与算法——栈、队列与双端队列

    栈 栈:是一种容器,可存入数据元素.访问元素.删除元素,它的特点在于只能允许在容器的一端进行加入数据和输出数据的运算.没有了位置概念,保证任何时候可以访问.删除的元素都是此前最后存入的那个元素,确定了 ...

  7. python 判断div 之间的内容是否为空_python实现数据结构与算法之双端队列实现

    简介 双端队列(deque, double-ended queue),是一种具有队列和栈的性质的数据结构.双端队列中的元素可以从两端弹出,其限定插入和删除操作在表的两端进行.双端队列可以在队列任意一端 ...

  8. 数据结构与算法(Python版) | (6) 线性结构---队列、双端队列和列表

    本专栏主要基于北大的数据结构与算法教程(Python版)进行整理,包括课程笔记和OJ作业. 课程链接 1. 队列抽象数据类型及Python实现 什么是队列? 队列是一种有次序的数据集合,其特征是: 1 ...

  9. 经典数据结构和算法 双端队列 java

    选一个简单的数据结构聊一聊,话说有很多数据结构都在玩组合拳,比如说:块状链表,块状数组,当然还有本篇的双端队列,是的,它就是 栈和队列的组合体. 一:概念 我们知道普通队列是限制级的一端进,另一端出的 ...

最新文章

  1. 再见,谷歌!再见,算法!
  2. java静态和动态的区别是什么意思_Java中的动态和静态多态性有什么区别?
  3. 注释数据库介绍之GO、KEGG数据库
  4. UILabel自适应高、宽
  5. 用C++流成员函数put输出单个字符
  6. 计算机桌面图标变成腾讯图标,桌面图标变成了未知图标
  7. .net 引用Com组件的几种方案
  8. EasyUI中dialog中嵌入form细节问题记录
  9. CentOS 7.6 安装 nginx,配置端口访问网站,切换root目录
  10. AtCode Beginner Contest 096
  11. Chrome 插件开发
  12. 考研微机原理是计算机基础吗,2015年电子科技大学微机原理与应用考研复试大纲...
  13. 帆软报表参数传给网络报表_在报表中给session赋值实现报表间参数共享
  14. 通信原理(三)香农三大定理
  15. 如何用 Ps 制作毛玻璃穿透效果?
  16. 使用HM NIS Edit制作软件安装包
  17. manifest权限
  18. 第七天 黑马十次方 吐槽列表与详细页、发吐槽与评论功能、问答频道功能、掌握DataURL和阿里云OSS
  19. 使用GitHub进行团队合作
  20. javaScript(call,apply,date,arguments,Math)

热门文章

  1. stm32f103VET6-FreerRTOS移植LWIP (enc28j60)
  2. 基于Luca-Kanade光流算法的图像运动场提取matlab仿真
  3. 基于单片机智能睡眠枕整套设计方案
  4. html 里面的 role 属性是什么意思
  5. role=“presentation“啥意思
  6. 关于BBS的一些资料
  7. 【jQuery】动效
  8. 女朋友乱用Git,差点把我代码删了。。。
  9. 分布式事务——两段式和三段式事务
  10. 可用!三行代码高仿高德地图三段式抽屉效果