算法竞赛:初级算法(第一章:基础数据结构)

这篇具有很好参考价值的文章主要介绍了算法竞赛:初级算法(第一章:基础数据结构)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

第一章:基础数据结构

1、链表

  • 动态链表

    • 动态链表需要临时分配链表节点,使用完毕后释放。

    • 优点:能及时释放空间,不使用多余内存

    • 缺点:需要管理空间,容易出错(竞赛一般不用动态链表)

    • #include<iostream>
      using namespace std;
      
      // n 个人围成一圈,从第一个人开始报数,数到m 的人出列,再由下一个人重新从1 开始报数,
      // 数到m 的人再出圈,依次类推,直到所有的人都出圈,请输出依次出圈人的编号。
      
      // 用动态链表实现
      
      // 结构
      struct Node {
          int data;       // 存储编号
          Node* next;     // 指向下一个节点的指针
      };
      typedef Node* pN;   // 定义指向Node的指针类型为pN
      
      int main() {
          int n, m;
          cin >> n >> m;
      
          // 建立循环链表
          pN head = new Node; // 创建头节点
          head->data = 1;
          head->next = nullptr;
          pN pCurrent = head; // 当前节点指针
      
          // 依次创建节点,构建循环链表
          for (int i = 2; i <= n; i++) {
              pN p = new Node;
              p->data = i;
              p->next = nullptr;
              pCurrent->next = p;
              pCurrent = p;
          }
      
          pCurrent->next = head; // 将链表闭合成循环
      
          // 循环计数、出列
          int k = 1;
          while (pCurrent != pCurrent->next) {
              if (k == m) {
                  pN p = pCurrent->next;
                  cout << p->data << " "; // 输出出列人的编号
                  pCurrent->next = p->next;
                  delete p; // 释放出列人的节点
                  k = 1;
              } else {
                  k++;
                  pCurrent = pCurrent->next;
              }
          }
          cout << pCurrent->data; // 输出最后一个出列人的编号
          delete pCurrent; // 释放最后一个节点
      
          return 0;
      }
      
  • 静态链表

    • 静态链表使用预先分配的一段连续空间存储链表,这种链表在逻辑上是成立的。

    • 有两种做法:

      • 定义链表结构体数组,和动态链表差不多。
      • 使用一维数组,直接在静态数组上实现。
    • 用结构体数组实现单向静态链表

      #include<iostream>
      using namespace std;
      
      // n 个人围成一圈,从第一个人开始报数,数到m 的人出列,再由下一个人重新从1 开始报数,
      // 数到m 的人再出圈,依次类推,直到所有的人都出圈,请输出依次出圈人的编号。
      
      const int N = 105;
      
      // 结构体定义
      struct Node {
          int id;        // 编号
          int nextid;    // 下一个节点的编号
      } nodes[N];
      
      int main() {
          int n, m;
          cin >> n >> m;
      
          // 初始化单向静态链表
          for (int i = 0; i < n - 1; i++) {
              nodes[i].id = i + 1;
              nodes[i].nextid = i + 1;
          }
          nodes[n - 1].id = n;
          nodes[n - 1].nextid = 0;
      
          int i = n - 1;
          int k = 1;
      
          // 循环计数、出列
          while (nodes[i].nextid != i) {
              if (k == m) {
                  int j = nodes[i].nextid;
                  nodes[i].nextid = nodes[j].nextid;
                  cout << nodes[j].id << " "; // 输出出列人的编号
                  k = 1;
              } else {
                  k++;
                  i = nodes[i].nextid;
              }
          }
      
          cout << nodes[i].id; // 输出最后一个出列人的编号
          return 0;
      }
      
      
    • 用一维数组实现单向静态链表

      #include<iostream>
      using namespace std;
      
      // n 个人围成一圈,从第一个人开始报数,数到m 的人出列,再由下一个人重新从1 开始报数,
      // 数到m 的人再出圈,依次类推,直到所有的人都出圈,请输出依次出圈人的编号。
      
      // 用一维数组实现单向静态链表
      // 定义一个 nodes[] 数组,nodes[i] 中的 i 就是结点的值,nodes[i] 里的数就是下一个节点
      int nodes[150];
      
      int main() {
          int n, m;
          cin >> n >> m;
      
          // 初始化单向静态链表
          for (int i = 1; i < n; i++) {
              nodes[i] = i + 1;
          }
          nodes[n] = 1;
      
          int now = n, prev = 1;
      
          // 循环计数、出列
          while (nodes[now] != now) {
              if (prev == m) {
                  cout << nodes[now] << " "; // 输出出列人的编号
                  nodes[now] = nodes[nodes[now]]; // 跳过当前出列的节点
              } else {
                  prev++;
                  now = nodes[now];
              }
          }
      
          return 0;
      }
      
  • STL list

    • 在算法竞赛或工程项目中经常使用C++STL list。它是双向链表,由标准模板库管理。通过迭代器访问节点数据,高效的删除和插入。

    • list的一些重要函数:

      • list容器的创建:假设是int型
        • 创建空容器std::list<int> values;
        • 创建包含n个元素的容器std::list<int> values(10);
        • 创建包含n个元素,并都赋初值的容器std::list<int> values(10, 5);
        • 拷贝已有list容器std::list<int> values2(values1);
      • 返回指向容器中第一个元素的双向迭代器values.begin();
      • 返回指向容器中最后一个元素所在位置的下一个位置的双向迭代器values.end();
      • 判断容器中是否有元素,若无元素,则返回 true;反之,返回 falsevalues.empty();
      • 返回当前容器实际包含的元素个数values.size();
      • 返回第一个元素的引用values.front();
      • 返回最后一个元素的引用values.back();
      • 在容器头部或尾部插入一个元素values.push_front(); values.push_back();
      • 删除容器头部或尾部的一个元素vlaues.pop_front(); values.pop_back();
      • 在容器中的指定位置插入元素vlaues.insert();
      • 删除容器中一个或某区域内的元素vlaues.erase();
    • #include<iostream>
      #include<list>
      using namespace std;
      
      // n 个人围成一圈,从第一个人开始报数,数到m 的人出列,再由下一个人重新从1 开始报数,
      // 数到m 的人再出圈,依次类推,直到所有的人都出圈,请输出依次出圈人的编号。
      
      // STL模板库list实现功能
      
      int main() {
          int n, m;
          cin >> n >> m;
      
          // 使用STL模板库中的list作为链表
          list<int> node;
      
          // 初始化链表
          for (int i = 1; i <= n; i++) {
              node.push_back(i);
          }
      
          // 迭代器指向链表头部
          list<int>::iterator it = node.begin();
      
          // 循环直到链表中只剩一个元素
          while (node.size() > 1) {
              // 循环报数,找到第m个元素
              for (int i = 1; i < m; i++) {
                  it++;
                  // 如果迭代器达到链表末尾,将其置为链表头部
                  if (it == node.end())
                      it = node.begin();
              }
      
              // 输出出列的人的编号
              cout << *it << " ";
      
              // 获取下一个元素的迭代器
              list<int>::iterator next = ++it;
      
              // 如果下一个迭代器达到链表末尾,将其置为链表头部
              if (next == node.end())
                  next = node.begin();
      
              // 删除出列的人
              node.erase(--it);
      
              // 更新迭代器为下一个元素
              it = next;
          }
      
          // 输出最后剩下的人的编号
          cout << *it;
      
          return 0;
      }
      

2、队列

​ 竞赛一般用STL queue或手写静态数组实现队列

  • STL queue

    • STL queue的主要操作:

      • 创建队列queue<int> q;
      • 将item插入队列q.push(item);
      • 返回队首元素q.front();
      • 删除队首元素q.pop();
      • 返回队尾元素q.back();
      • 返回元素个数q.size();
      • 检查队列是否为空q.empty();
    • #include<iostream>
      #include<queue>
      using namespace std;
      
      // 假设内存中有 M 个单元,每单元能存放一个单词和译义。每当软件将一个新单词存入内存前,如果当前内存中已存入的单词数不超过 M−1,
      // 软件会将新单词存入一个未使用的内存单元;若内存中已存入 M 个单词,软件会清空最早进入内存的那个单词,腾出单元来,存放新单词。
      // 假设一篇英语文章的长度为 N 个单词。给定这篇待译文章,翻译软件需要去外存查找多少次词典?假设在翻译开始前,内存中没有任何单词。
      
      // 使用哈希表和队列存单词,查单词用哈希表查,用队列腾出单元
      
      int Hash[1000] = { 0 };
      
      int main() {
          int m, n;
          cin >> m >> n;
          int cnt = 0;
      
          // 使用队列保存内存中的单词顺序
          queue<int> q;
      
          for (int i = 1; i <= n; i++) {
              int t;
              cin >> t;
      
              // 如果单词未在内存中,将其存入内存
              if (Hash[t] == 0) {
                  cnt++;
                  Hash[t] = 1;
                  q.push(t);
      
                  // 如果内存中单词数超过 M,腾出最早进入内存的单元
                  if (q.size() > m) {
                      Hash[q.front()] = 0;
                      q.pop();
                  }
              }
          }
      
          // 输出外存查找次数
          cout << cnt;
      
          return 0;
      }
      
      
  • 手写循环队列

    • 下面是循环队列的手写模板,竞赛一般用静态分配空间

    • #include<iostream>
      using namespace std;
      
      // 假设内存中有 M 个单元,每单元能存放一个单词和译义。每当软件将一个新单词存入内存前,如果当前内存中已存入的单词数不超过 M−1,
      // 软件会将新单词存入一个未使用的内存单元;若内存中已存入 M 个单词,软件会清空最早进入内存的那个单词,腾出单元来,存放新单词。
      // 假设一篇英语文章的长度为 N 个单词。给定这篇待译文章,翻译软件需要去外存查找多少次词典?假设在翻译开始前,内存中没有任何单词。
      
      // 使用哈希表和手写循环队列存单词,查单词用哈希表查,用队列腾出单元
      
      const int N = 1000;
      int Hash[N] = { 0 };
      
      // 手写循环队列结构体
      struct my_queue {
          int data[N];
          int head, rear;
      
          // 构造队列
          my_queue() : head(0), rear(0) {};
      
          // 返回队列长度
          int size() { return (rear - head + N) % N; }
      
          // 判空操作
          bool empty() {
              return size() == 0;
          }
      
          // 入队
          bool push(int e) {
              if ((rear + 1) % N == head)
                  return false;
              data[rear] = e;
              rear = (rear + 1) % N;
              return true;
          }
      
          // 出队
          bool pop() {
              if (head == rear) {
                  return false;
              }
              head = (head + 1) % N;
              return true;
          }
      
          // 返回队头元素
          int front() {
              return data[head];
          }
      } q;
      
      int main() {
          int m, n;
          cin >> m >> n;
          int cnt = 0;
      
          for (int i = 1; i <= n; i++) {
              int t;
              cin >> t;
              if (Hash[t] == 0) {
                  cnt++;
                  Hash[t] = 1;
                  q.push(t);
      
                  // 如果队列长度超过 M,腾出最早进入队列的元素
                  if (q.size() > m) {
                      Hash[q.front()] = 0;
                      q.pop();
                  }
              }
          }
      
          // 输出外存查找次数
          cout << cnt;
      
          return 0;
      }
      
  • 双端队列和单调队列

    • 双端队列和单调队列的概念

      • 双端队列是具有队列和栈性质的数据结构,它能且只能在两端插入或删除
      • STL的双端队列deque的用法如下:
        • 创建双端队列deque<int> dq;
        • 返回队头dq.front();
        • 返回队尾dq.back();
        • 删除队头dq.pop_front();
        • 删除队尾dq.pop_back();
        • 在队头添加一个元素edq.push_front(e);
        • 在队尾添加一个元素edq.push_back(e);
      • 单调队列是双端队列的经典应用,单调队列里的元素是单调有序的
      • 单调队列里的元素的顺序和原来在序列中的顺序是一致的;
    • 单调队列的应用:滑动窗口

      #include<iostream>
      #include<deque>
      using namespace std;
      
      // 有一个长为 n 的序列 a,以及一个大小为 k 的窗口。窗口从左边开始向右滑动,
      // 每次滑动一个单位,求每次滑动后窗口中的最大值和最小值。
      
      // 如果每次移动窗口,都要检查 k 个数来计算最大值和最小值,那么它就要检查 O(nk) 次。
      // 可以使用单调队列来优化移动窗口,每次移动窗口时,对应的单调队列就要丢弃和增加相应元素,并输出最大值和最小值(需要两个单调队列)。
      // 优化后只需要检查 3n 次。
      
      // 单调递增队列,保存元素下标
      deque<int> maxDeque;
      // 单调递减队列,保存元素下标
      deque<int> minDeque;
      
      // 主函数
      int main() {
          // 输入序列长度和窗口大小
          int n, k;
          cin >> n >> k;
          
          // 输入序列
          int list[1000000];
          for (int i = 0; i < n; i++) {
              cin >> list[i];
          }
      
          int head = 0, rear = 0;
      
          // 处理单调递增队列
          for (; rear <= k - 1; rear++) {
              int t = list[rear];
              // 保持队列单调递增,丢弃比当前元素小的元素
              while (!maxDeque.empty() && list[maxDeque.back()] < t) {
                  maxDeque.pop_back();
              }
              maxDeque.push_back(rear);  // 将当前元素下标加入队列
          }
          cout << list[maxDeque.front()] << " ";  // 输出当前窗口的最大值
          rear--;
      
          // 继续处理后续窗口
          while (rear != n - 1) {
              rear++;
              int t = list[rear];
              // 保持队列单调递增,丢弃比当前元素小的元素
              while (!maxDeque.empty() && list[maxDeque.back()] < t) {
                  maxDeque.pop_back();
              }
              maxDeque.push_back(rear);  // 将当前元素下标加入队列
              // 检查队首元素是否仍在当前窗口内,不在则出队
              if (maxDeque.front() == head) {
                  maxDeque.pop_front();
              }
              cout << list[maxDeque.front()] << " ";  // 输出当前窗口的最大值
              head++;
          }
          cout << endl;
      
          head = rear = 0;
      
          // 处理单调递减队列
          for (; rear <= k - 1; rear++) {
              int t = list[rear];
              // 保持队列单调递减,丢弃比当前元素大的元素
              while (!minDeque.empty() && list[minDeque.back()] > t) {
                  minDeque.pop_back();
              }
              minDeque.push_back(rear);  // 将当前元素下标加入队列
          }
          cout << list[minDeque.front()] << " ";  // 输出当前窗口的最小值
          rear--;
      
          // 继续处理后续窗口
          while (rear != n - 1) {
              rear++;
              int t = list[rear];
              // 保持队列单调递减,丢弃比当前元素大的元素
              while (!minDeque.empty() && list[minDeque.back()] > t) {
                  minDeque.pop_back();
              }
              minDeque.push_back(rear);  // 将当前元素下标加入队列
              // 检查队首元素是否仍在当前窗口内,不在则出队
              if (minDeque.front() == head) {
                  minDeque.pop_front();
              }
              cout << list[minDeque.front()] << " ";  // 输出当前窗口的最小值
              head++;
          }
      
          return 0;
      }
      
  • 优先队列

    • 优先队列由堆组成,有最小优先队列和最大优先队列,每个出队元素都是队内最小或最大值。
    • STL优先队列的操作:
      • 大顶堆的创建:priority_queue<int> max_pq;
      • 小顶堆的创建:priority_queue<int, vector<int>, greater<int>> min_pq;
      • 将元素插入到优先队列中max_pq.push(item);
      • 弹出优先队列堆顶:max_pq.pop();
      • 返回优先队列堆顶元素:max_pq.top();
      • 返回优先队列中的元素个数:max_pq.size();
      • 检查优先队列是否为空:max_pq.empty();

3、栈

  • STL stack

    • 竞赛一般直接用STLstack,或这自己手写静态栈。

    • STL stack的有关操作:

      • 栈的创建stack<int> s;
      • 将元素存入栈顶s.push(item);
      • 返回栈顶元素s.top(item);
      • 删除栈顶元素s.pop(item);
      • 返回栈中元素个数s.size();
      • 检查栈是否为空s.empty();
    • #include<iostream>
      #include<stack>
      using namespace std;
      
      // 反转字符串
      // 使用 STL stack 来完成操作
      
      int main() {
          stack<char> s;
          char c;
      
          // 从标准输入逐字符读取,直到遇到换行符
          c = getchar();
          while (c != '\n') {
              if (c == ' ' && !s.empty()) {
                  // 如果遇到空格且栈非空,输出栈内字符,并加上空格
                  while (!s.empty()) {
                      char t;
                      t = s.top();
                      s.pop();
                      cout << t;
                  }
                  cout << " ";
              }
              else {
                  // 非空格字符入栈
                  s.push(c);
              }
              // 继续读取下一个字符
              c = getchar();
          }
      
          // 处理最后一个单词
          if (!s.empty()) {
              while (!s.empty()) {
                  char t;
                  t = s.top();
                  s.pop();
                  cout << t;
              }
              cout << " ";
          }
      
          return 0;
      }
      
      
  • 手写栈

    • 手写静态栈代码简单且节省空间

    • #include<iostream>
      using namespace std;
      
      //反转字符串
      
      //使用STLstack来完成操作
      const int N = 100100;
      struct my_stack {
      	char a[N];
      	int t = 0;
      	void push(char x) { a[++t] = x; }
      	void pop() { t--; }
      	char top() { return a[t]; }
      	int empty() { return t == 0 ? 1 : 0; }
      }s;
      int main() {
      	char c;
      	c = getchar();
      	while (c != '\n') {
      		if (c == ' ' && !s.empty()) {
      			while (!s.empty()) {
      				char t;
      				t = s.top();
      				s.pop();
      				cout << t;
      			}
      			cout << " ";
      		}
      		else {
      			s.push(c);
      		}
      		c = getchar();
      	}
      	if (!s.empty()) {
      		while (!s.empty()) {
      			char t;
      			t = s.top();
      			s.pop();
      			cout << t;
      		}
      		cout << " ";
      	}
      	return 0;
      }
      
  • 单调栈

    • 单调栈中的元素是单调递增或单调递减的,比如递减栈从栈顶到栈底是从大到小排序的。

    • 单调栈的操作:比如递减栈,当一个元素入栈时,与栈顶比较,若比栈顶小,则入栈,若比栈顶大,这弹出栈顶,直到比栈顶元素小,则入栈。注意,每个数都会入栈

    • #include<iostream>
      #include<stack>
      using namespace std;
      
      // N(1 << N << 10^5)头奶牛站成一排,奶牛i的身高为Hi(1 <= Hi <= 1000000)。
      // 现在,每头奶牛都在向右看齐。对于奶牛i,如果奶牛j满足i < j 且Hi < Hj,我们就说奶牛i仰望奶牛j。
      // 求出每头奶牛离他最近的仰望对象
      
      // 暴力解法:遍历序列,在每个奶牛的序列右边找到它的仰望对象,复杂度在所有奶牛都没有仰望对象的情况下是O(nlog2N)
      
      // 单调栈优化:由于单调栈的特性是每个元素都入栈,且栈内元素都是单调性的,所有可以用它来优化暴力
      // 从后往前遍历序列,每次将元素压入单调栈时,栈顶元素就是该元素的仰望对象,栈空返回0,复杂度是O(n)
      
      int a[100001]; // 存储奶牛的身高
      int b[100001]; // 存储每头奶牛的仰望对象
      
      int main() {
          int n;
          cin >> n;
      
          // 输入每头奶牛的身高
          for (int i = 1; i <= n; i++) {
              int t;
              cin >> t;
              a[i] = t;
          }
      
          stack<int> s; // 单调栈,用于存储奶牛的序号
      
          // 从后往前遍历奶牛序列,求解每头奶牛的仰望对象
          for (int i = n; i >= 1; i--) {
              // 单调栈维护单调递减的序号
              while (!s.empty() && a[s.top()] <= a[i]) {
                  s.pop();
              }
      
              // 如果栈为空,表示没有比当前奶牛更高的奶牛,仰望对象为0
              if (s.empty()) {
                  b[i] = 0;
                  s.push(i);
              } else {
                  // 栈顶元素即为当前奶牛的仰望对象
                  b[i] = s.top();
                  s.push(i);
              }
          }
      
          // 输出每头奶牛的仰望对象
          for (int i = 1; i <= n; i++) {
              cout << b[i] << " ";
          }
      
          return 0;
      }
      
      

4、二叉树和哈夫曼树

  • 二叉树的存储结构

    • 动态二叉树

      • struct Node{
            int value;	//节点的值
            node* lson, *rson;	//指向左右孩子的指针
        };
        
      • 动态新建一个Node时,需要手动分配内存,使用完毕后,还要释放它,以防内存泄漏。

      • 动态二叉树的优点是不浪费空间,缺点是需要管理,容易出错。

    • 静态二叉树

      • struct Node{
            char value;		//节点的值
            int lson, rson;	//左右孩子的坐标
        }tree[N];
        
      • 由于静态二叉树编码简单,在算法竞赛中一般使用静态二叉树。

      • 编码时一般不用tree[0],因为0被用来表示空节点,当一个节点的左右子树为空时就可以表示为lson = rson = 0;

      • 特别的,当用数组实现完全二叉树时,可以不用定义lson,rson。一颗节点总数为k的完全二叉树,设一号节点为根节点,有以下性质:

        • 编号i > 1的节点,其父结点编号是i / 2。
        • 如果2i > k,说明节点 i 没有孩子;如果2i + 1 > k, 那么节点 i 没有右孩子。
        • 如果节点 i 有孩子,那么它的左孩子是2i,它的右孩子是2i + 1。
    • 多叉树转化为二叉树

      • 将多叉树的第一个孩子作为右孩子,将其兄弟孩子作为右孩子。
  • 哈夫曼编码以及哈夫曼树

    • 哈夫曼编码是一种用于数据压缩的算法,该编码算法基于一种称为哈夫曼树的数据结构,通过根据输入文本中字符的出现频率构建一棵二叉树,并将频率较高的字符用较短的二进制码表示,频率较低的字符用较长的二进制码表示,以实现对数据的高效压缩。

    • 哈夫曼编码的主要步骤如下:

      • 统计输入文本中每个字符的出现频率。

      • 将每个字符及其频率构建为一个包含单节点的二叉树。

      • 将这些二叉树合并为一棵哈夫曼树,合并过程中频率较低的树作为新树的左子树,频率较高的树作为右子树。

      • 在哈夫曼树的叶子节点上的每个字符都被赋予一个唯一的二进制编码,路径的左分支表示0,右分支表示1。

      • 使用得到的编码对输入文本中的每个字符进行替换,生成压缩后的二进制数据。

    • 由于哈夫曼编码的构建过程保证了没有编码是另一个编码的前缀,所以在解码时不会出现歧义。哈夫曼编码在信息论和数据压缩领域有广泛应用,例如在无损压缩算法中,以及在通信领域中的数据传输和存储中。

    • 哈夫曼树的性质:文章来源地址https://www.toymoban.com/news/detail-802987.html

      • 哈夫曼树的所有叶子节点相加就是字符串长度
      • 哈夫曼树的所有非叶子节点相加就是字符串的哈夫曼编码长度
    • #include<iostream>
      #include<queue>
      #include<string>
      using namespace std;
      
      // 输入一个字符串,分别用普通ASCII编码(每个字符8bit)和哈夫曼编码
      // 输出编码前后的长度,并输出压缩比
      
      // 本题的核心问题是求字符串的哈夫曼编码长度
      // 在哈弗曼树中,所有非叶子节点相加即为字符串的哈夫曼编码长度
      // 题目没有要求输出哈夫曼编码,因此不必真的构造哈夫曼树
      // 只需要一个变量,初始值为0,每次将两个最小频率的字符的频率之和加到变量中
      // 直到所有字符都加完,最后的变量值就是字符串的哈夫曼编码长度
      
      // 计算字符串中每个字符的频率
      // 将字符串存入字符数组中,对数组进行排序
      // 从前往后读字符,读到相同的字符频率加一,直到读到不同的字符结束
      // 此时的频率就是字符的频率
      
      // 哈夫曼编码长度的计算
      // 构造一个小顶堆优先队列(可以直接使用STL优先队列)
      // 每次计算完一个频率就放入优先队列,直到处理完所有字符
      // 设定一个变量,初始值为0
      // 每次出队两个频率,将这两个频率相加再放入队列
      // 令变量等于频率之和加上这个变量,直到优先队列为空
      // 此变量就是哈夫曼长度
      
      int main() {
          priority_queue<int, vector<int>, greater<int>> q; // 小顶堆优先队列,用于构建哈夫曼编码
          string s;
          cin >> s;
          
          while (s != "END") {
              sort(s.begin(), s.end()); // 将字符串排序以便统计字符频率
      
              int num = 1;
              // 统计字符频率
              for (int i = 1; i <= s.size(); i++) {
                  if (s[i] != s[i - 1]) {
                      q.push(num);
                      num = 1;
                  } else {
                      num++;
                  }
              }
      
              int ans = 0;
              // 计算哈夫曼编码长度
              while (q.size() != 1) {
                  int a = q.top();
                  q.pop();
                  a = a + q.top();
                  q.pop();
                  q.push(a);
                  ans += a;
              }
              q.pop();
      
              // 输出编码前后的长度和压缩比
              cout << "Original Length: " << s.size() * 8 << " bits, Huffman Length: " << ans
                   << " bits, Compression Ratio: " << (double)s.size() * 8 / (double)ans << endl;
      
              cin >> s; // 读取下一个字符串
          }
      
          return 0;
      }
      
      

到了这里,关于算法竞赛:初级算法(第一章:基础数据结构)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++算法之旅、04 基础篇 | 第一章 基础算法

    cstdio 有两个函数 printf,scanf 用于输出和输入 iostream 有 cin 读入,cout 输出 使用了std命名空间,cin、cout定义在该命名空间中,不引入空间会找不到导致出错 函数执行入口 ⭐所有 cout、cin 都能用 scanf、printf 替换,但反过来,由于cout、cin效率可能较低会导致超时 ⭐ printf %c 会读

    2024年02月10日
    浏览(45)
  • 数据结构笔记(王道考研) 第一章:绪论

    大部分内容基于中国大学MOOC的2021考研数据结构课程所做的笔记,该课属于付费课程(不过盗版网盘资源也不难找。。。)。后续又根据23年考研的大纲对内容做了一些调整,将二叉排序树和平衡二叉树的内容挪到了查找一章,并增加了并查集、平衡二叉树的删除、红黑树的内

    2024年02月14日
    浏览(49)
  • 数据结构预习笔记第一章-数据结构的概念

    重点理解 数据结构的定义 , 逻辑结构 , 存储结构 , 算法的时间效率分析和算法的空间效率分析 2.1 什么是数据结构 概念😵 数据 :所有的数字,字符和能够被输入到计算机中进行运算的符号的集合。 数据元素 :数据元素是数据的 基本单位 ❗️,在计算机中通常是作为

    2024年01月25日
    浏览(57)
  • 【蓝桥杯备赛Java组】第一章·语言基础|竞赛常用库函数|输入输出|String的使用|常见的数学方法|大小写转换

    🎥 个人主页:深鱼~ 🔥收录专栏:蓝桥杯 🌄欢迎 👍点赞✍评论⭐收藏 目录 一、编程基础 1.1 Java类的创建  1.2 Java方法  1.3 输入输出  1.4 String的使用 二、竞赛常用库函数 1.常见的数学方法 2.大小写转换 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,

    2024年01月19日
    浏览(79)
  • C++算法之旅、04 基础篇 | 第一章

    cstdio 有两个函数 printf,scanf 用于输出和输入 iostream 有 cin 读入,cout 输出 使用了std命名空间,cin、cout定义在该命名空间中,不引入空间会找不到导致出错 函数执行入口 ⭐所有 cout、cin 都能用 scanf、printf 替换,但反过来,由于cout、cin效率可能较低会导致超时 ⭐ printf %c 会读

    2024年02月10日
    浏览(40)
  • 【头歌-Python】Python第一章作业(初级)

    任务描述 示例 Python 可以方便的实现计算器的功能。数学意义上的加、减、乘、除在Python中分别以符号“+、-、*、/”表示。 试编程实现分两行输入两个非零浮点数,并在4 行中按顺序输出两个数的加、减、乘、除的计算式和计算结果。计算结果str.format()方法严格保留小数点后

    2024年02月02日
    浏览(121)
  • 广工anyview数据结构第一章(2021.12)

    广工anyview数据结构习题第一章, 在学习过程中部分题目参考了Giyn 、戮漠、雁过留痕等大佬的代码,在此感谢。 题目解法不是最优解,但希望能给大家有所启发。同时也发了文档资源,需要可自取。 如果对你有帮助,可以给卑微的博主留个赞、关注、收藏   (不是)  (骗一

    2024年02月07日
    浏览(106)
  • 第一百零五天学习记录:数据结构与算法基础:顺序表(王卓教学视频)

    注:笔记截图均来自王卓数据结构教学视频 线性表是具有相同特性的数据元素的一个有限序列 同一线性表中的元素必定具有相同特性,数据元素间的关系是线性关系。 稀疏多项式的运算 顺序存储结构存在的问题 1、存储空间分配不灵活 2、运算的空间复杂度高 引出链式存储

    2024年02月15日
    浏览(38)
  • 第一百零六天学习记录:数据结构与算法基础:单链表(王卓教学视频)

    结点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻 线性表的链式表示又称为非顺序映像或链式映像。 用一组物理位置任意的存储单元来存放线性表的数据元素。 这组存储单元既可以是连续的,也可以是不连续的,甚至是零散分布在内存中的任意

    2024年02月16日
    浏览(54)
  • 【软考数据库】第一章 计算机系统基础知识

    目录 目录 1.1 计算机系统 1.1.1 计算机硬件组成 1.1.2 中央处理单元 1.1.3 数据表示 1.1.4 校验码 1.2 计算机体系结构 1.2.1 体系结构分类 1.2.2 指令系统存 1.2.3 储系系统 1.2.4 输入/输出技术 1.2.5 总线结构 1.3 可靠性、性能、安全 1.3.1 计算机可靠性 1.3.2 计算机系统的性能评价 1.

    2023年04月13日
    浏览(109)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包