C語言中:遞歸問題的深入研究

C語言中:遞歸問題的深入研究

函數的遞歸有兩個限制條件:

1.遞歸存在限制條件,當滿?這個限制條件的時候,遞歸便不再繼續。

2.每次遞歸調?之后越來越接近這個限制條件。

例子:

#include <stdio.h>
int main()
{printf("haha\n");main();//自己調用自己的主函數return 0;
}
//出現死循環的打印

這是為什么呢?

這是遞歸的==常見錯誤:==棧溢出

  • 淺講一下:
  • 每次調用函數的時候都會向內存申請空間,在C語言中內存一般劃分為三個區域:棧區堆區靜態區,代碼區。
  • 在寫代碼的時候要創建變量,局部變量創建申請的內存空間在棧區上面(還有函數的形參);

堆區里面放的是動態開辟的內存(比如malloc、calloc);

全局變量的創建放在靜態區里面(包括static修飾的變量)。

函數的調用都需要從棧區里面申請內存空間。

  • 我們調用main函數的時候就要從棧區里面分配一塊內存空間,調用printf函數的時候也從棧區分配一塊內存空間,接下來又遇到main函數又要從棧區分配一塊內存空間。然后陷入死循環,不斷從棧區里面申請空間,當空間耗盡的時候,就出現錯誤:(棧溢出)

  • 在C語?中每?次函數調?,都要需要為本次函數調?在棧區申請?塊內存空間來保存函數調?期間的各種局部變量的值,這塊空間被稱為運?時堆棧,或者函數棧幀。函數不返回,函數對應的棧幀空間就?直占?。所以如果函數調?中存在遞歸調用的話,每?次遞歸函數調?都會開辟屬于自己的棧幀空間,直到函數遞歸不再繼續,開始回歸,才逐層釋放棧幀空間。所以如果采?函數遞歸的?式完成代碼,遞歸層次太深,就會浪費太多的棧幀空間,也可能引起棧溢出(stack over flow)的問題。

函數棧幀(運行時堆棧)的本質

1.棧幀的作用
  • 每個函數調用在棧區分配的內存塊稱為棧幀(Stack Frame),用于存儲:
    • 局部變量:函數內定義的變量(如int a, char b)
    • 形參值:調用函數時傳遞的參數副本。
    • 返回地址:函數執行完畢后,返回調用者的下一條指令地址。
    • 上下文信息:保存函數調用前的寄存器狀態(如ebp、esp,用于恢復調用者的棧幀)。
2.棧幀的生命周期
  • 創建:函數調用時,系統從棧頂分配空間(棧向下增長,堆向上增長)。
  • 銷毀:函數返回時,系統釋放棧幀空間,棧頂指針回退。
3. 棧幀結構示例(x86 架構)

高地址
┌───────────────┐
│ 調用者棧幀 │
├─────────────── ┤
│ 返回地址 │ <- 調用者的下一條指令地址
├─────────────── ┤
│ 形參值 │ <- 按從右到左順序壓棧(C語言特性)
├───────────────┤
│ 保存的ebp │ <- 調用者的ebp寄存器值
├───────────────┤
│ 局部變量 │ <- 函數內定義的變量
├───────────────┤
│ 臨時變量 │ <- 表達式計算臨時值
└───────────────┘ <- ebp(當前棧幀基址)
低地址

遞歸調用與棧幀的關系

1.遞歸的棧幀特征

  • 每層遞歸都是獨立棧幀:每次遞歸調用都會創建新的棧幀,保存當前層的部變量、形參和返回地址。

  • 棧幀數量等于遞歸深度:若遞歸深度為n,則棧中存在n個未釋放的棧幀。

2.階乘遞歸的棧幀變化

int factorial(int n)
{if (n == 0) return 1;return n * factorial(n-1); // 遞歸調用
}

當調用factorial(3)時,棧幀變化如下:
第 1 層:n=3,調用factorial(2),棧幀 1 入棧。
第 2 層:n=2,調用factorial(1),棧幀 2 入棧。
第 3 層:n=1,調用factorial(0),棧幀 3 入棧。
終止層:n=0,返回 1,棧幀 3 釋放,返回棧幀 2。
回歸層:棧幀 2 計算2*1,釋放后返回棧幀 1,依此類推。

  1. 棧幀的空間占用

每個棧幀的大小由函數內局部變量決定。例如:

void func() {int a[1000]; // 占用4KB(假設int占4字節)char b;      // 占用1字節
}

每次調用func需分配約 4KB 棧空間。

三、棧溢出(Stack Overflow)的成因與風險

1.直接原因

  • 遞歸深度過大:如計算factorial(10000),若每層棧幀占 1KB,總需約 10MB 空間,遠超默認棧大小(通常 8MB 以下)。
  • 局部變量過大:函數內定義超大數組(如int arr[1000000]),單個棧幀耗盡棧空間。

2.系統默認棧大小

  • Linux:通過ulimit -s查看,默認通常為 8192 KB(8MB)。
  • Windows:Visual Studio 默認約 1MB(可通過鏈接器選項調整)。

四、避免棧溢出的策略

  1. 限制遞歸深度
  • 尾遞歸優化:若遞歸調用是函數最后一步操作(尾遞歸),部分編譯器(如 GCC,需加-O2參數)可復用棧幀,避免棧增長。

  • int factorial_tail(int n, int result)
    {if (n == 0) return result;return factorial_tail(n-1, n*result); // 尾遞歸,可優化
    }
    int factorial(int n) {return factorial_tail(n, 1);
    }
    
  1. 手動模擬遞歸(迭代法):

    • 用循環和顯式棧(如數組、鏈表)替代遞歸,避免依賴系統棧。
  2. 最佳實踐:
    優先使用迭代,除非遞歸能顯著簡化代碼(如樹 / 圖的遍歷)。
    對遞歸深度可預估且較小的場景(如n < 1000),可直接使用遞歸。
    對深四、使用動態內存(堆)模擬棧
    手動用堆內存實現棧結構,替代函數調用棧(即 “遞歸轉迭代”)。
    示例:用堆模擬棧計算階乘

  3. 度可能較大的場景(如未知輸入的遞歸),必須用迭代或尾遞歸優化。

  4. 使用動態內存(堆)模擬棧,手動用堆內存實現棧結構,替代函數調用棧(即 “遞歸轉迭代”)。

    #include <stdio.h>
    #include <stdlib.h>typedef struct Stack
    {int* data;int top;int capacity;
    }Stack;Stack* create_stack(int size) 
    {Stack* stack = (Stack*)malloc(sizeof(Stack));stack->data = (int*)malloc(size * sizeof(int));stack->top = -1;stack->capacity = size;return stack;
    }void push(Stack* stack, int val)
    {if (stack->top < stack->capacity - 1){stack->data[++stack->top] = val;}
    }int pop(Stack* stack) 
    {if (stack->top >= 0) {return stack->data[stack->top--];}return -1;  // 錯誤處理
    }int factorial(int n) 
    {if (n == 0) return 1;Stack* stack = create_stack(n);int result = 1;// 入棧:模擬遞歸調用while (n > 1) {push(stack, n);n--;}// 出棧:模擬遞歸回歸while (stack->top >= 0) {result *= pop(stack);}free(stack->data);free(stack);return result;
    }
    
  • 棧結構定義與初始化
typedef struct Stack
{int* data;     // 動態數組存儲棧元素int top;       // 棧頂指針(初始為-1表示空棧)int capacity;  // 棧的最大容量
} Stack;Stack* create_stack(int size) 
{Stack* stack = (Stack*)malloc(sizeof(Stack));  // 分配棧結構內存stack->data = (int*)malloc(size * sizeof(int)); // 分配數據存儲區stack->top = -1;      // 初始化棧頂指針stack->capacity = size;  // 設置棧容量return stack;
}
  • 關鍵點:

    • 動態內存分配:通過兩次malloc分別分配棧結構和數據區
    • 棧頂指針:top初始為 - 1,表示棧為空;入棧時先自增再賦值
  • 棧操作實現

void push(Stack* stack, int val)
{if (stack->top < stack->capacity - 1)  // 檢查棧未滿{stack->data[++stack->top] = val;  // 先移動棧頂指針,再存入數據}
}int pop(Stack* stack) 
{if (stack->top >= 0)  // 檢查棧非空{return stack->data[stack->top--];  // 先返回數據,再移動棧頂指針}return -1;  // 錯誤處理(棧空時返回-1)
}
  • 操作邏輯:
    • 入棧(Push):棧頂指針top先自增,再將值存入data[top]
    • 出棧(Pop):先返回data[top]的值,再將top自減
      邊界檢查:入棧時檢查top < capacity-1,出棧時檢查top >= 0
  • 階乘計算的棧模擬
int factorial(int n) 
{if (n == 0) return 1;  // 基準條件:0! = 1Stack* stack = create_stack(n);  // 創建容量為n的棧int result = 1;// 階段1:入棧過程(模擬遞歸調用)while (n > 1) {push(stack, n);  // 將n壓入棧n--;             // n遞減,模擬遞歸深入}// 階段2:出棧過程(模擬遞歸回歸)while (stack->top >= 0) {result *= pop(stack);  // 彈出棧頂元素并累乘到結果}free(stack->data);  // 釋放數據區內存free(stack);        // 釋放棧結構內存return result;
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/82806.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/82806.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/82806.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

《C++20新特性全解析:模塊、協程與概念(Concepts)》

引言&#xff1a;C20——現代C的里程碑 C20是繼C11之后最具革命性的版本&#xff0c;它通過模塊&#xff08;Modules&#xff09;、協程&#xff08;Coroutines&#xff09;和概念&#xff08;Concepts&#xff09;三大核心特性&#xff0c;徹底改變了C的代碼組織方式、并發模…

xcode卡死問題,無論打開什么程序xcode總是在轉菊花,重啟電腦,卸載重裝都不行

很可能是因為我們上次沒有正常關閉Xcode&#xff0c;而Xcode保留了上次錯誤的一些記錄&#xff0c;而這次打開Xcode依然去加載錯誤的記錄&#xff0c;所以必須完全刪除這些記錄Xcode才能加載正常的項目。 那么也就是說&#xff0c;我們是不是只需要刪除這部分錯誤記錄文件就可以…

華為云Flexus+DeepSeek征文|華為云Flexus云服務器X實例上部署Dify:打造高效的開源大語言模型應用開發平臺

目錄 前言 1 Dify與華為云部署概述 1.1 什么是 Dify 1.2 華為云與 Flexus 云服務器的優勢 2 云服務器部署 Dify 的步驟詳解 2.1 模板選擇 2.2 參數配置 2.3 資源棧設置 2.4 確認部署信息并執行 3 部署成功后的操作與平臺使用指南 3.1 訪問平臺 3.2 設置管理員賬號 …

物流項目第九期(MongoDB的應用之作業范圍)

本項目專欄&#xff1a; 物流項目_Auc23的博客-CSDN博客 建議先看這期&#xff1a; MongoDB入門之Java的使用-CSDN博客 需求分析 在項目中&#xff0c;會有兩個作業范圍&#xff0c;分別是機構作業范圍和快遞員作業范圍&#xff0c;這兩個作業范圍的邏輯是一致的&#xf…

網絡拓撲如何跨網段訪問

最近領導讓研究下跟甲方合同里的&#xff0c;跨網段訪問怎么實現&#xff0c;之前不都是運維網工干的活么&#xff0c;看來裁員裁到動脈上了碰到用人的時候找不到人了&#xff0c; 只能趕鴨子上架讓我來搞 IP 網絡中&#xff0c;不同網段之間的通信需要通過路由器&#xff0c;…

【前端】PWA

目錄 概述實戰vue項目問題匯總 PWA&#xff08;漸進式 Web 應用&#xff0c;Progressive Web App&#xff09; 2015提出 概述 PWA 是一種提升 Web 應用體驗的技術&#xff0c;使其具備與原生應用相似的功能和性能。PWA不僅能夠在網頁上運行&#xff0c;還能在手機或桌面上像傳…

湖北理元理律師事務所:從法律合規到心靈契合的服務升維

債務優化不僅是數字游戲&#xff0c;更是信任重建的過程。湖北理元理律師事務所在實踐中發現&#xff1a;68%的債務糾紛中存在溝通斷裂。為此&#xff0c;機構構建了“三維信任修復機制”。 維度一&#xff1a;信息透明的技術實現 區塊鏈存證艙&#xff1a;客戶手機實時查看律…

香橙派3B學習筆記2:Vscode遠程SSH登錄香橙派_權限問題連接失敗解決

Vscode下載插件&#xff0c;ssh遠程登錄香橙派。 ssh &#xff1a; orangepi本地ip 密碼 &#xff1a; orangepi 安裝 Remote - SSH 擴展SSH插件&#xff1a; SSH遠程連接&#xff1a; ssh usernameremote_host ssh -p port_number usernameremote_host默認22端口號就用第一行…

VMware安裝Ubuntu實戰分享大綱

深入解析快速排序 一、分治策略分解 分解階段&#xff1a; 選擇基準元素 $pivot$將數組劃分為三個子集&#xff1a; $$ left {x | x < pivot} $$ $$ equal {x | x pivot} $$ $$ right {x | x > pivot} $$ 遞歸排序&#xff1a; 對 left 和 right 子集遞歸調用快速排…

AI 讓無人機跟蹤更精準——從視覺感知到智能預測

AI 讓無人機跟蹤更精準——從視覺感知到智能預測 無人機跟蹤技術正在經歷一場前所未有的變革。曾經,我們只能依靠 GPS 或簡單的視覺識別來跟蹤無人機,但如今,人工智能(AI)結合深度學習和高級視覺算法,正讓無人機的跟蹤變得更加智能化、精準化。 尤其是在自動駕駛、安防監…

GATED DELTA NETWORKS : IMPROVING MAMBA 2 WITH DELTA RULE

TL;DR 2024 年 Nvidia MIT 提出的線性Transformer 方法 Gated DeltaNet&#xff0c;融合了自適應內存控制的門控機制&#xff08;gating&#xff09;和用于精確內存修改的delta更新規則&#xff08;delta update rule&#xff09;&#xff0c;在多個基準測試中始終超越了現有…

Laravel單元測試使用示例

Date: 2025-05-28 17:35:46 author: lijianzhan 在 Laravel 框架中&#xff0c;單元測試是一種常用的測試方法&#xff0c;它是允許你測試應用程序中的最小可測試單元&#xff0c;通常是方法或函數。Laravel 提供了內置的測試工具PHPUnit&#xff0c;實踐中進行單元測試是保障代…

【FastAPI】--3.進階教程(二)

【FastAPI】--進階教程1-CSDN博客 【FastAPI】--基礎教程-CSDN博客 目錄 1.FastAPI - CORS ?2.FastAPI - CRUD 操作 2.1.Create 2.2.Read 2.3.Update 2.4.Delete 3.FastAPI - 使用 GraphQL 4.FastAPI - Websockets 5.FastAPI - 事件處理程序 6.FastAPI - 安裝 Fla…

FEMFAT許可的更新與升級流程

隨著工程仿真技術的不斷發展&#xff0c;FEMFAT作為一款領先的疲勞分析軟件&#xff0c;持續為用戶提供卓越的性能和創新的功能。為了保持軟件的最新性和高效性&#xff0c;了解FEMFAT許可的更新與升級流程至關重要。本文將為您詳細介紹FEMFAT許可的更新與升級流程&#xff0c;…

麒麟v10,arm64架構,編譯安裝Qt5.12.8

Window和麒麟x86_64架構&#xff0c;官網提供安裝包&#xff0c;麒麟arm64架構的&#xff0c;只能自己用編碼編譯安裝。 注意&#xff0c;“桌面”路徑是中文&#xff0c;所以不要把源碼放在桌面上編譯。 1. 下載源碼 從官網下載源碼&#xff1a;https://download.qt.io/arc…

20250528-C#知識:結構體

C#知識&#xff1a;結構體 結構體是一種自定義數據類型&#xff0c;用戶可以根據自身需求設計自己的結構體用來表示某種數據集合。結構體是一種值類型&#xff0c;結合了值類型的優點&#xff0c;避免了引用類型的缺點。本文簡單介紹并探究一下C#中的結構體。 結構體一般寫在命…

CRM系統的功能模塊劃分

基礎管理模塊 用戶管理 用戶注冊與登錄角色權限管理部門組織架構用戶信息管理 系統設置 基礎參數配置系統日志管理數據字典管理系統監控 客戶管理模塊 客戶信息管理 客戶基本信息客戶分類管理客戶標簽管理客戶關系圖譜 聯系人管理 聯系人信息聯系記錄溝通歷史重要日期提醒 …

Python中的跨域資源共享(CORS)處理

在Web開發中&#xff0c;跨域資源共享&#xff08;CORS&#xff09;是瀏覽器強制執行的安全機制&#xff0c;用于控制不同源&#xff08;協議域名端口&#xff09;之間的資源交互。下面我將通過Python示例詳細講解CORS的實現。 原生Python實現CORS Flask框架手動實現CORS fr…

Kruskal算法剖析與py/cpp/Java語言實現

Kruskal算法剖析與py/cpp/Java語言實現 一、Kruskal算法的基本概念1.1 最小生成樹1.2 Kruskal算法核心思想 二、Kruskal算法的執行流程三、Kruskal算法的代碼實現3.1 Python實現3.2 C實現3.3 Java實現 四、算法復雜度分析4.1 時間復雜度4.2 空間復雜度 五、Kruskal算法應用場景…

微信小程序返回上一頁監聽

本文實現的是微信小程序在返回上一頁時獲取通知并自定義業務。 最簡單的實現&#xff1a; 使用 wx.enableAlertBeforeUnload() 優點&#xff1a;快速接入 缺點&#xff1a;手勢不能識別、無法自定義彈窗內容&#xff08;僅詢問&#xff09; 方法二&#xff1a; page-conta…