国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - C/C++ - C語言 如何用堆解決Topk問題

C語言 如何用堆解決Topk問題

2022-03-08 15:23檸檬葉子C C/C++

TopK問題即在N個數中找出最大的前K個,這篇文章將詳細講解如何利用小根堆的方法解決TopK問題,文中代碼具有一定參考價值,快跟隨小編一起學習一下吧

前言

本篇講解如何利用小根堆的方法解決TopK問題,這么多數據要處理,該算法時間復度居然只需:

C語言 如何用堆解決Topk問題

 

TopK問題

TopK問題介紹:在N個數中找出最大的前K個 (比如在1000個數中找出最大的前10個)

 

解題方法

方法1:先排降序,前N個就是最大的。 

時間復雜度:  

C語言 如何用堆解決Topk問題

方法2:N個數依次插入大堆,HeapPop K次,每次取堆頂的數據,即為前K個。

時間復雜度:

C語言 如何用堆解決Topk問題

假設N非常大,N是10億,內存中存不下這些數,它們存在文件中的。K是100,方法1 和 方法2 就都不能用了……

話說 10 億個整數,大概占用多少空間?

1G = 1024MB

1G = 1024*1024KB

1G = 1024*1024*1024Byte

要占用10億字節!所以我們來看看方法3。

方法3:

① 用前個K數建立一個K個數的小堆。

② 剩下的N-K個數,依次跟堆頂的數據進行比較。如果比堆頂的數據大,就替換堆頂的數據,再向下調整。

③ 最后堆里面的K個數就是最大的K個數。

時間復雜度: 

C語言 如何用堆解決Topk問題

這里為什么使用小堆而不使用大堆?

最大的前K個數一定會比其他數要大,只要進來的數比堆頂數據大,就替代它。因為是小堆(小的在上大的在下),最大的數進去后一定會沉到下面,所以不可能存在大的數堵在堆頂導致某個數進不去的情況,數越大沉得越深。對應地,如果使用大堆就會出現一個大數堵在堆頂,剩下的數都比這個大數小,導致其他數進不來,最后只能選出最大的那一個。

 

代碼實現與講解

由于還沒開始講 C++ ,這里我們沒法用優先級隊列,我們得手動自己寫一個堆來使用。當然,如果自己懶得寫,以下是 C語言 實現堆的代碼。

Heap.h

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

typedef int HPDataType;

typedef struct Heap {
  HPDataType* array;  //指向動態開辟的數組
  int size;           //有效數據的個數
  int capacity;       //容量空間的大小
} HP;

/* 堆的初始化 */
void HeapInit(HP* php);

/* 堆的銷毀 */
void HeapDestroy(HP* php);

/* 堆的打印 */
void HeapPrint(HP* php);

/* 判斷堆是否為空 */
bool HeapIfEmpty(HP* hp);

/* 堆的插入 */
void HeapPush(HP* php, HPDataType x);
  /* 檢查容量 */
  void HeapCheckCapacity(HP* php);
      /* 交換函數 */
      void Swap(HPDataType* px, HPDataType* py);
  /* 大根堆上調 */ 
  void BigAdjustUp(int* arr, int child);
  /* 小根堆上調 */ 
  void SmallAdjustUp(int* arr, int child);

/* 堆的刪除 */
void HeapPop(HP* php);
  /* 小根堆下調*/ 
  void SmallAdjustDown(int* arr, int n, int parent);
  /* 大根堆下調 */
  void BigAdjustDown(int* arr, int n, int parent);

/* 返回堆頂數據*/
HPDataType HeapTop(HP* php);

/* 統計堆的個數 */
int HeapSize(HP* php);

Heap.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "Heap.h"

/* 堆的初始化 */
void HeapInit(HP* php) {
  assert(php);
  php->array = NULL;
  php->size = php->capacity = 0;
}

/* 堆的銷毀 */
void HeapDestroy(HP* php) {
  assert(php);
  free(php->array);
  php->capacity = php->size = 0;
}

/* 堆的打印 */
void HeapPrint(HP* php) {
  for (int i = 0; i < php->size; i++) {
      printf("%d ", php->array[i]);
  }
  printf("\n");
}

/* 判斷堆是否為空 */
bool HeapIfEmpty(HP* php) {
  assert(php);

  return php->size == 0; // 如果為size為0則表示堆為空
}

/* 堆的插入 */
  /* 檢查容量 */ 
  void HeapCheckCapacity(HP* php) {
      if (php->size == php->capacity) {
          int newCapacity = php->capacity == 0 ? 4 : (php->capacity * 2); //第一次給4,其他情況擴2倍
          HPDataType* tmpArray = (HPDataType*)realloc(php->array, sizeof(HPDataType) * newCapacity); // 數組擴容
          if (tmpArray == NULL) {  //檢查realloc
              printf("realloc failed!\n");
              exit(EXIT_FAILURE);
          }
          //更新他們的大小
          php->array = tmpArray;
          php->capacity = newCapacity;
      }
  }

      /* 交換函數 */ 
      void Swap(HPDataType* px, HPDataType* py) {
          HPDataType tmp = *px;
          *px = *py;
          *py = tmp;
      } 

  /* 大根堆上調 */ 
  void BigAdjustUp(int* arr, int child) {
      assert(arr);
      // 首先根據公式計算算出父親的下標
      int parent = (child - 1) / 2;
      // 最壞情況:調到根,child=parent 當child為根節點時結束(根節點永遠是0)
      while (child > 0) {
          if (arr[child] > arr[parent]) {  // 如果孩子大于父親(不符合堆的性質)
              // 交換他們的值
              Swap(&arr[child], &arr[parent]);
              // 往上走
              child = parent;
              parent = (child - 1) / 2;
          } else {  // 如果孩子小于父親(符合堆的性質)
          // 跳出循環
              break;
          }
      }
  }

  /* 小根堆上調 */ 
  void SmallAdjustUp(int* arr, int child) {
      assert(arr);
      // 首先根據公式計算算出父親的下標
      int parent = (child - 1) / 2;
      // 最壞情況:調到根,child=parent 當child為根節點時結束(根節點永遠是0)
      while (child > 0) {
          if (arr[child] < arr[parent]) {  // 如果孩子大于父親(不符合堆的性質)
              // 交換他們的值
              Swap(&arr[child], &arr[parent]);
              // 往上走
              child = parent;
              parent = (child - 1) / 2;
          } else {  // 如果孩子小于父親(符合堆的性質)
          // 跳出循環
              break;
          }
      }
  }
void HeapPush(HP* php, HPDataType x) {
  assert(php);
  // 檢查是否需要擴容
  HeapCheckCapacity(php);
  // 插入數據
  php->array[php->size] = x;
  php->size++;
  // 向上調整 [目標數組,調整位置的起始位置(剛插入的數據)]
  SmallAdjustUp(php->array, php->size - 1);
}

/* 堆的刪除 */

  /* 小根堆下調*/ 
  void SmallAdjustDown(int* arr, int n, int parent) {
      int child = parent * 2 + 1; // 默認為左孩子
      while (child < n) { // 葉子內
          // 選出左右孩子中小的那一個
          if (child + 1 < n && arr[child + 1] < arr[child]) {
              child++;
          }
          if (arr[child] < arr[parent]) { // 如果孩子小于父親(不符合小堆的性質)
              // 交換它們的值
              Swap(&arr[child], &arr[parent]);
              // 往下走
              parent = child;
              child = parent * 2 + 1;
          } else { // 如果孩子大于父親(符合小堆的性質)
              // 跳出循環
              break;
          }
      }
  }

  /* 大根堆下調 */
  void BigAdjustDown(int* arr, int n, int parent) {
      int child = parent * 2 + 1; // 默認為左孩子
      while (child < n) { // 葉子內
          // 選出左右孩子中大的那一個
          if (child + 1 < n && arr[child + 1] > arr[child]) {
              child++;
          }
          if (arr[child] > arr[parent]) { // 如果孩子大于父親(不符合大堆的性質)
              // 交換它們的值
              Swap(&arr[child], &arr[parent]);
              // 往下走
              parent = child;
              child = parent * 2 + 1;
          } else { // 如果孩子小于父親(符合大堆的性質)
              // 跳出循環
              break;
          }
      }
  }
void HeapPop(HP* php) {
  assert(php);
  assert(!HeapIfEmpty(php));
  // 刪除數據
  Swap(&php->array[0], &php->array[php->size - 1]);
  php->size--;
  // 向下調整 [目標數組,數組的大小,調整位置的起始位置]
  SmallAdjustDown(php->array, php->size, 0);
}

/* 返回堆頂數據 */
HPDataType HeapTop(HP* php) {
  assert(php);
  assert(!HeapIfEmpty(php));

  return php->array[0];
}

/* 統計堆的個數 */
int HeapSize(HP* php) {
  assert(php);

  return php->size;
}

第三種方法的參考代碼:

#define _CRT_SECURE_NO_WARNINGS 1
#include "Heap.h"

/* 在N個數中找出最大的前K個 */
void PrintTopK(int* arr, int N, int K) {

  HP hp;                             // 創建堆
  HeapInit(&hp);                     // 初始化堆

  for (int i = 0; i < K; i++) {      // Step1: 創建一個K個數的小堆
      HeapPush(&hp, arr[i]);
  }

  for (int i = K; i < N; i++) {      // Step2: 剩下的N-K個數跟堆頂的數據比較
      if (arr[i] > HeapTop(&hp)) {   // 如果比堆頂的數據大就替換進堆
          HeapPop(&hp);              // 此數確實比堆頂大,刪除堆頂
          HeapPush(&hp, arr[i]);     // 將此數推進堆中,數越大下沉越深
          /* 另一種寫法: 手動替換
          hp.array[0] = arr[i];
          SmallAdjustDown(hp.array, hp.size, 0);
          */
      }
  }
  HeapPrint(&hp);                    // 打印K個數的堆
  HeapDestroy(&hp);                  // 銷毀堆
}

/* 模擬測試 TopK */
void TestTopK() {
  int N = 1000000;
  int* arr = (int*)malloc(sizeof(int) * N);

  srand(time(0)); // 置隨機數種子
  for(size_t i = 0; i < N; i++) {
      // 產生隨機數,每次產生的隨機數都mod100w,這樣產生的數一定是小于100w的
      arr[i] = rand() % 1000000; 
  }
  
  // 再去設置10個比100w大的數
  arr[5] = 1000000 + 1;
	arr[1231] = 1000000 + 2;
	arr[5355] = 1000000 + 3;
	arr[51] = 1000000 + 4;
	arr[15] = 1000000 + 5;
	arr[2335] = 1000000 + 6;
	arr[9999] = 1000000 + 7;
	arr[76] = 1000000 + 8;
	arr[423] = 1000000 + 9;
	arr[3144] = 1000000 + 10;

  PrintTopK(arr, N, 10); //測試用,所以給10個
}

int main(void) {
  TestTopK();

	return 0;
}

 

運行結果

C語言 如何用堆解決Topk問題

 

函數解讀

PrintTopK 解讀

① 準備好我們實現好的堆之后,我們就可以寫TopK的算法了。我們創建一個 PrintTopK 函數,函數需要接收存放數據的數組、數組的大?。∟)和要找前多少個(K)。

② 首先創建一個堆,用來存放K 。按照規矩我們先把 HeapInit(初始化)和 HeapDestroy(銷毀)先寫好,防止自己不小心忘記銷毀。

③ 核心步驟1:創建一個K個數的小堆,我們直接用 for 循環將數組中前K個值先逐個 HeapPush (堆的插入)進去。

這里不代表最后的結果,我們只是相當于 "默認" 認為這前K個數是最大的,方便我們后續進行比較替代。經過 HeapPush (堆的插入)后,這些數據會通過 SmallAdjustDown (小堆下調接口) 對數據進行相應的調整:

for (int i = 0; i < K; i++) {      // Step1: 創建一個K個數的小堆
  HeapPush(&hp, arr[i]);
}

④ 核心步驟2:除去K,將剩下的N-K個數據進行比較。我們再利用 for 循環將數組中剩下的N-K個數據進行遍歷。

這里逐個進行判斷,如果該數堆頂的數據 i<K(max),我們就進行替換操作。調用 HeapPop(堆的刪除)刪除堆頂的數據,給  讓位。之后將其 HeapPush (堆的插入)推到堆中,就完成了替換的工作。值得一提的是,我們還可以不調用 Pop 和 Push 這兩個操作,手動進行替換。hp.array [ 0 ] 就表示棧頂,我們將  賦值給它,隨后再手動進行 SmallAdjustDown (小堆下調操作),傳入相應的值即可:

for (int i = K; i < N; i++) {      // Step2: 剩下的N-K個數跟堆頂的數據比較
  if (arr[i] > HeapTop(&hp)) {   // 如果比堆頂的數據大就替換進堆
      HeapPop(&hp);              // 此數確實比堆頂大,刪除堆頂
      HeapPush(&hp, arr[i]);     // 將此數推進堆中,數越大下沉越深
      /* 另一種寫法: 手動替換
      hp.array[0] = arr[i];
      SmallAdjustDown(hp.array, hp.size, 0);
      */
  }
}

⑤ 當 for 遍歷完所有數據之后,小堆中就保留了N個數據中最大的前K個數了。此時我們調用堆打印接口將小堆里的數據打印出來就大功告成了。

TestTopK 解讀

① 這是用來測試我們寫的TopK算法的函數。設置 N 的大小為 100w,動態內存開辟一塊可以存下這么多數據的空間:

int N = 1000000;
int* arr = (int*)malloc(sizeof(int) * N);

② 隨后根據時間來置隨機數種子,將每個元素都進行隨機數的填充,每次產生的隨機數都模上100w,這樣可以保證產生的隨機數一定是小于100w的。

srand(time(0));
for(size_t i = 0; i < N; i++) {
  arr[i] = rand() % 1000000; 
}

③ 隨便寫幾個大于100w的數,便于測試:

// 再去設置10個比100w大的數
arr[5] = 1000000 + 1;
arr[1231] = 1000000 + 2;
arr[5355] = 1000000 + 3;
arr[51] = 1000000 + 4;
arr[15] = 1000000 + 5;
arr[2335] = 1000000 + 6;	
arr[9999] = 1000000 + 7;	
arr[76] = 1000000 + 8;
arr[423] = 1000000 + 9;
arr[3144] = 1000000 + 10;

④ 調用我們剛才實現好的 PrintTopK 函數,遞交對應的參數后就可以進行測試了。這里為了方便測試,我們的K設置為10:

PrintTopK(arr, N, 10);

到此這篇關于C語言 如何用堆解決Topk問題的文章就介紹到這了,更多相關Topk問題內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!

原文鏈接:https://blog.csdn.net/weixin_50502862/article/details/121610001

延伸 · 閱讀

精彩推薦
  • C/C++C語言實現電腦關機程序

    C語言實現電腦關機程序

    這篇文章主要為大家詳細介紹了C語言實現電腦關機程序,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下...

    xiaocaidayong8482021-08-20
  • C/C++學習C++編程的必備軟件

    學習C++編程的必備軟件

    本文給大家分享的是作者在學習使用C++進行編程的時候所用到的一些常用的軟件,這里推薦給大家...

    謝恩銘10102021-05-08
  • C/C++深入理解goto語句的替代實現方式分析

    深入理解goto語句的替代實現方式分析

    本篇文章是對goto語句的替代實現方式進行了詳細的分析介紹,需要的朋友參考下...

    C語言教程網7342020-12-03
  • C/C++c++ 單線程實現同時監聽多個端口

    c++ 單線程實現同時監聽多個端口

    這篇文章主要介紹了c++ 單線程實現同時監聽多個端口的方法,幫助大家更好的理解和學習使用c++,感興趣的朋友可以了解下...

    源之緣11542021-10-27
  • C/C++C++之重載 重定義與重寫用法詳解

    C++之重載 重定義與重寫用法詳解

    這篇文章主要介紹了C++之重載 重定義與重寫用法詳解,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下...

    青山的青6062022-01-04
  • C/C++C語言中炫酷的文件操作實例詳解

    C語言中炫酷的文件操作實例詳解

    內存中的數據都是暫時的,當程序結束時,它們都將丟失,為了永久性的保存大量的數據,C語言提供了對文件的操作,這篇文章主要給大家介紹了關于C語言中文件...

    針眼_6702022-01-24
  • C/C++C/C++經典實例之模擬計算器示例代碼

    C/C++經典實例之模擬計算器示例代碼

    最近在看到的一個需求,本以為比較簡單,但花了不少時間,所以下面這篇文章主要給大家介紹了關于C/C++經典實例之模擬計算器的相關資料,文中通過示...

    jia150610152021-06-07
  • C/C++詳解c語言中的 strcpy和strncpy字符串函數使用

    詳解c語言中的 strcpy和strncpy字符串函數使用

    strcpy 和strcnpy函數是字符串復制函數。接下來通過本文給大家介紹c語言中的strcpy和strncpy字符串函數使用,感興趣的朋友跟隨小編要求看看吧...

    spring-go5642021-07-02
Weibo Article 1 Weibo Article 2 Weibo Article 3 Weibo Article 4 Weibo Article 5 Weibo Article 6 Weibo Article 7 Weibo Article 8 Weibo Article 9 Weibo Article 10 Weibo Article 11 Weibo Article 12 Weibo Article 13 Weibo Article 14 Weibo Article 15 Weibo Article 16 Weibo Article 17 Weibo Article 18 Weibo Article 19 Weibo Article 20 Weibo Article 21 Weibo Article 22 Weibo Article 23 Weibo Article 24 Weibo Article 25 Weibo Article 26 Weibo Article 27 Weibo Article 28 Weibo Article 29 Weibo Article 30 Weibo Article 31 Weibo Article 32 Weibo Article 33 Weibo Article 34 Weibo Article 35 Weibo Article 36 Weibo Article 37 Weibo Article 38 Weibo Article 39 Weibo Article 40
主站蜘蛛池模板: 久久精品亚洲精品国产欧美 | 精产国产伦理一二三区 | 亚洲另类视频 | 日本不卡高字幕在线2019 | 曰韩中文字幕 | 国产精品无码永久免费888 | 久久九精品 | 日本高清无卡码一区二区久久 | 国产日皮视频 | 先锋影音av在线 | 国产精品自产拍在线观看 | 亚洲二区在线播放 | 久久精品亚洲 | 精品视频一区二区三区四区 | 欧美久久久久久久 | 成人在线免费观看 | 久久久久久成人 | 国产在线精品一区二区三区 | 亚洲人成网站999久久久综合 | 国产欧美精品一区二区三区四区 | 一区二区国产精品 | 99精品一区二区三区 | 一级欧美一级日韩 | 天堂俺去俺来也www久久婷婷 | 免费a级毛片大学生免费观看 | 成人久久久精品国产乱码一区二区 | 国产高清在线精品一区二区三区 | 久久久毛片| 国产综合久久 | 欧洲免费视频 | 国产精品二区三区 | 在线观看自拍 | 欧美日韩视频 | 欧美视频一区二区 | 成年人免费小视频 | 成人在线手机版视频 | 99热这里有 | 日本精品1区2区 | 欧美一区二区三区视频 | 天天射天天干 | 爱爱视频网址 |