1. 程式人生 > >OpenCL與CNN篇二:OpenCL基礎API介紹

OpenCL與CNN篇二:OpenCL基礎API介紹

  1. 本篇介紹幾個OpenCL基礎API,涉及平臺、裝置等初始化必備函式
  2. 其次介紹幾個關於緩衝區操作以及工作空間劃分的API
  3. 建議閱讀參考書籍,我的推薦是《OpenCL程式設計指南》和《OpenCL異構計算》,尤其是後者從實踐出發,更是適合上手。
  4. 本章節函式使用舉例見系列篇三,這裡不再重複;僅介紹API相關引數,以及自己的見解。

一、OpenCL平臺API

平臺API定義了宿主機程式發現OpenCL裝置所用的函式以及這些函式的功能,另外還定義了為OpenCL
應用建立上下文的函式。包括平臺、裝置、上下文等相關函式。
不過為了方便起見這裡將構建程式和建立核心也放在了此章節,即1.4、1.5、1.6。
注意1.7及之後為一些篇外話,不作為和1.1~1.6的連續內容。

1.1、獲取平臺clGetPlatformIDs()

cl_int clGetPlatformIDs( 
       cl_uint          num_entries,    //限制返回的平臺數
       cl_platform_id  *platforms,      //平臺列表儲存位置指標
       cl_uint         *num_platforms   //平臺個數儲存位置指標
        );

將num_entries和platforms分別設定為0和NULL可以查詢可用的平臺個數,儲存在num_platforms中。返回的平臺數可以用num_entries來限制,獲取平臺列表時這個引數要大於0並小於等於可用平臺數。
平臺這個概念應該是不同廠商的實現,比如我一開始安裝了AMD APP SDK,執行程式只有1個可用平臺;後來有想法就又安裝了Intel的SDK,這時執行OpenCL有兩個可用平臺。區別是一個平臺包含GPU和CPU,一個平臺只包含CPU;而且列印裝置資訊,他們包含的CPU名稱相同。

1.2、獲取裝置clGetDeviceIDs()

cl_int clGetDeviceIDs(
       cl_platform_id  platform,       // 指定平臺
       cl_device_type  device_type,    // 指定裝置型別
       cl_uint         num_entries, 
       cl_device_id   *devices,    
       cl_uint        *num_devices
        );

這個命令會得到與platform關聯的eOpenCL裝置列表。如果引數devices為NULL,則用num_devices得到可用裝置數。類似平臺函式用num_entries可以限制返回的裝置數。
引數device_type用來指定計算裝置型別,其極常見引數選擇見下表。

cl_device_type 描述
CL_DEVICE_TYPE_CPU 作為宿主機處理器的OpenCL裝置
CL_DEVICE_TYPE_GPU 作為GPU的OpenCL裝置
CL_DEVICE_TYPE_ACCELERATOR OpenCL加速器(例如,IBM Cell Broaband)
CL_DEVICE_TYPE_DEFAULT 預設裝置
CL_DEVICE_TYPE_ALL 與相應平臺關聯的所有OpenCL裝置

1.3、建立上下文clCreateContext()

cl_context clCreateContext(
     const cl_context_properties *properties,    // 此處包含平臺資訊
           cl_uint                num_devices,   // 裝置個數
     const cl_device_id          *devices,       // 裝置列表
     void (CL_CALLBACK  *pfn_notify)(const char *errinfo,const void *private_info,size_t cb, void *user_data),
     void                        *user_data,
     cl_int                      *errcode_ret
     );

第一引數著重說明一下,需要用確定的平臺來構建,方法如下:
  cl_context_properties props[3] =
  { CL_CONTEXT_PLATFORM , (cl_context_properties)platformIds, 0 };
第四個引數那麼複雜就直接忽略吧,因為呼叫時那個位置填NULL。使用中user_data也輸入NULL。最後一個引數errcode_ret用來記錄錯誤程式碼,正常時errcode_ret==CL_SUCCESS。

1.4、建立程式物件clCreateProgramWithSource()

cl_program clCreateProgramWithSource(
           cl_context     context,
           cl_uint        count,
           const char   **string,
           const size_t  *lengths,
           cl_int        *errcode_ret
           );

context 建立程式物件的上下文
count 沒看到過介紹
const strings 將這個引數的所有字元,構成了建立程式物件的完整原始碼
lengths 這個引數可以設定為NULL,這種情況下,則認為字串以null終止的
errcode_ret 如果非NULL,函式返回錯誤程式碼將由這個引數返回
一般執行時,先從.cl檔案中讀取程式碼,然後將儲存程式碼的字串傳遞給strings

1.5、構建clBuildProgram()

cl_int clBuildProgram(
       cl_program          program,       // 一個合法的程式物件
       cl_uint             num_devices,   // 要構建程式物件的裝置數
       const cl_device_id *device_list,   // 如果為空則為context所有關聯裝置建立
       const char         *options,
       void(CL_CALLBACK   *pfn_notify)(cl_program program,void *user_data),
       void               *user_data
       );

函式的第四、五、六個引數設定為NULL即可,前三個引數根據程式碼段註釋傳遞即可。深入學習的話關於第四個引數options,可以參考《OpenCL程式設計指南》6.2.2或者檢視文章開頭給的參考網頁。

1.6、建立核心clCreateKernel()

cl_kernel clCreateKernel(
          cl_program  program,
          const char *kernel_name,
          cl_int     *errcode_ret
          );

到這裡就比較容易了,只介紹一下kernel_name:建立核心物件的核心函式名。這是程式原始碼__kernel關鍵字後面的核心函式名。引數errcode_ret的作用和上面的API中意義相同。

1.7、篇外話之先建立上下文再獲取裝置

  • a、建立上下文clCreateContextFromType()
  • b、從上下文中查詢裝置資訊clGetContextInfo()
cl_context clCreateContextFromType(
     const cl_context_properties *properties,
     cl_device_type               device_type,
     void (CL_CALLBACK *pfn_notify)(const char *,const void *p,size_t, void *),
     void                        *user_data,
     cl_int                      *errcode_ret           
     );
cl_int clCreatContextInfo(
       cl_Context      context,
       cl_Context_info param_name,
       size_t          param_value_size,
       void           *param_value,
       size_t         *param_value_size_ret
       )

和clCreateContext()類似,不過這裡不需要事先指定裝置列表,使用例項:
  platform = platformIds[0];
  cl_context_properties props[3] =
  { CL_CONTEXT_PLATFORM,(cl_context_properties)platform, 0 };
  context = clCreateContextFromType( props, CL_DEVICE_TYPE_CPU, NULL, NULL, &errNum);
之後需要在上下文中查詢裝置clGetContextInfo(),獲取到裝置列表之後操作就和之前相同了,使用例項:
  cl_uint numDevices ;
  cl_device_id *deviceIds;
  errNum = clGetContextInfo( context, CL_CONTEXT_DEVICES, 0, NULL, &numDevices);
  deviceIds =(cl_device_id*)malloc(sizeof(cl_device_id)*numDevices);
  errNum = clGetContextInfo( context, CL_CONTEXT_DEVICES, numDevices, deviceIds, NULL);
把這一個篇外話,是因為這有一個不可取之處,這樣搞會有很奇怪的問題。首先在我的電腦上如果是用clCreateContextFromType建立上下文裝置選擇CL_DEVICE_TYPE_CPU;在呼叫clGetContextInfo獲取的numDevices=4,這是我CPU的執行緒數,並不是CPU個數。這樣本來我還能接受,因為只選擇deviceIds[0]的話程式可以正常執行;但如果列印裝置資訊,deviceIds[0]的資訊能正常獲取並且printf出來,但是獲取deviceIds[1]時就開始報錯了,簡直。。。。。
把這一個篇外話,只是為了在實踐中理解上下文和平臺裝置之間的關係,並不是推薦使用。

二、OpenCL執行時API

這些API管理上下文來建立命令佇列以及執行時發生的其他操作。包括建立讀寫緩衝區、設定執行核心等

2.1、設定核心引數clSetKernelArg()

cl_int clSetKernelArg(
       cl_kernel   kernel,
       cl_uint     arg_index,
       size_t     *arg_size,
       const void *arg_value
       );

kernel 一個合法的核心物件;arg_index 核心引數的索引;arg_size 引數的大小;arg_value 傳入核心函式的引數的一個指標。這裡引數的意思看看就好,重要的是看例子學會使用。設定好kernel引數之後通過呼叫下文的clEnqueueNDRangeKernel來執行。
值得說明的是如果你要多次呼叫執行同一個核心,那麼可以只設置一次核心。例如我寫卷積神經網路,在程式開始設定核心引數,之後訓練過程中只需把新的影象寫入到影象緩衝區,直接呼叫clEnqueueNDRangeKernel執行核心就好。

2.2、執行核心clEnqueueNDRangeKernel()

cl_int clEnqueueNDRangeKernel(
       cl_command_queue commad_queue,
       cl_kernel        kernel,
       cl_uint          work_dim,
       const size_t    *global_work_offset,
       const size_t    *global_work_size,
       const size_t    *local_work_size,
       cl_uint          num_events_in_wait_list,
       const cl_event  *events_in_wait_list,
       cl_event        *event
       );

commend_queue 核心的執行需要提交到命令佇列中
kernel 要執行的核心名稱
work_dim 指定新建的work-item的緯度,這裡我假設你已經知道了OpenCL的核心與工作項之間的關係。
global_work_offset 為work-item提供全域性ID,該引數可以不從0開始,但一般設定為0或者NULL
global_work_size 指定NDRange中每維work-item的數量,不可為空
local_work_size 指定workgroup中每維work-item的數量,可以設定為NULL讓系統自動設定
num_events_in_wait_list、events_in_wait_list、event 這是OpenCL高階一點的操作,用於記錄此事件或者需要等待的其他事件,可以用來規劃不同事件執行順序;如果不用可以分別設定為0、NULL、NULL。
需要注意的是global_work_size指向的陣列大小要和work_dim相等,global_work_offset和local_work_size不為NULL時也是同樣要求,不一樣會發生什麼我也沒有測試,不過正常的程式這裡該是一樣的。

三、緩衝區的建立、寫入和讀取

3.1、flush和finish命令

這兩個命令在命令佇列中值兩種不同型別的barrier操作。clFinish()函式阻塞直到命令佇列中所有命令完成。clFlush()阻塞指導命令佇列中的所有命令被移出佇列,這意味著這些命令已經準備就緒但無法保證執行完畢。
cl_int clFlush(cl_command_queue command_queue);
cl_int clFinish(cl_command_queue command_queue);

3.2、建立緩衝區clCreateBuffer()

cl_mem clCreateBuffer(
       cl_context   context,
       cl_mem_flags flag,
       size_t       size,
       void        *host_ptr,
       cl_int      *errcode_ret
       );

context 一個合法的上下文,為這個上下文分配緩衝區
size 所分配緩衝區的大小(位元組數)
host_ptr 這個指標在clCreateBuffer如何使用有flags引數確定。host_ptr指向的資料大小應大於等於size
flags 用於指定關於緩衝區建立的分配和使用資訊。其部分取值見下表。

cl_mem_flags 描述
CL_MEM_READ_WRITE 指定記憶體物件將由核心讀寫;預設為此模式
CL_MEM_WRITE_ONLY 指定記憶體物件由核心寫,但不能讀。
CL_MEM_READ_ONLY 指定記憶體物件由核心讀,但不能寫。
CL_MEM_USE_HOST_PTR 只有當host_ptr為非NULL時,這個標誌合法;使用host_ptr引用的記憶體作為記憶體物件的儲存位
CL_MEM_ALLOC_HOST_PTR 指定緩衝區應當在宿主機可訪問的記憶體中分配。不可與USE_HOST_PTR同時使用
CL_MEM_COPY_HOST_PTR 表示希望OpenCL實現分配記憶體物件的記憶體,並從host_ptr引用複製資料。

3.3、讀寫緩衝區

cl_int clEnqueueWriteBuffer(
       cl_command_queue commad_queue,
       cl_mem           buffer,
       cl_bool          blocking_write,
       size_t           offset,
       size_t           cb,
       void            *ptr,
       cl_uint          num_events_in_wait_list,
       const cl_event  *events_in_wait_list,
       cl_event        *event
       );
cl_int clEnqueueReadBuffer(
       cl_command_queue commad_queue,
       cl_mem           buffer,
       cl_bool          blocking_read,
       size_t           offset,
       size_t           cb,
       void            *ptr,
       cl_uint          num_events_in_wait_list,
       const cl_event  *events_in_wait_list,
       cl_event        *event
          );

command_queue 這是一個命令佇列,讀寫命令將在這個佇列中排隊
buffer 一個合法的緩衝區物件(資料對這裡讀寫)
blocking_read 如果設定為CL_TRUE,則命令阻塞,直至ptr讀寫資料完成
offset 緩衝區物件的讀寫資料的起始偏移量(位元組數)
cb 對緩衝區讀寫的位元組數
ptr 宿主機記憶體的一個指標,寫入緩衝區的資料從哪裡來 / 或者從緩衝區讀資料寫入哪裡
關於讀寫的blocking_write的多一些說明:
  如果blocking_write為CL_TRUE,
    則OpenCL實現將複製ptr引用的資料,並在命令佇列中對寫操作進行排隊。
    在clEnqueueWriteBuffer呼叫返回後,由ptr指向的記憶體可以被應用程式重用。
  如果blocking_write為CL_FALSE,
    則OpenCL實現將使用ptr執行非阻塞寫操作。 由於寫是非阻塞的,實現可以立即返回。
    ptr指向的記憶體在呼叫返回後不能被應用程式重用。
    event引數返回一個事件物件,可以用來查詢write命令的執行狀態。
    當寫命令完成後,ptr指向的記憶體可以被應用程式重新使用

四、影象建立讀寫

影象型別的資料我還沒有用過,不過好像挺有用的;在此只給出API的介紹。

4.1、影象格式

typedef struct _cl_image_format
{
    cl_channel_order image_channel_order;
    cl_channel_type  iamge_channel_date_type; 
}   cl_image_format ;

The image format describes how the data will be stored in memory
使用示例:
cl_image_format format;
format.image_channel_order = CL_R; // single channel
format.image_channel_data_type = CL_FLOAT; // float data type

4.2、影象建立

cl_mem clCreateImage2D(
       cl_context             context,
       cl_mem_flags           flags,
       const cl_image_format *image_format
       size_t                 image_with,
       size_t                 image_height,
       size_t                 image_row_pitch,
       void                  *host_ptr,
       cl_int                *errcode_ret
          );
cl_mem clCreateImage3D(
       cl_context             context,
       cl_mem_flags           flags,
       const cl_image_format *image_format
       size_t                 image_with,
       size_t                 image_height,
       size_t                 image_depth,
       size_t                 image_row_pitch,
       size_t                *image_slice_pitch,
       void                  *host_ptr,
       cl_int                *errcode_ret
          );

context 建立影象物件的上下文
flags 其合法列舉由cl_mem_flags定義
image_format 描述通道次序和影象通道資料型別
image_with,image_height,image_depth 影象的長寬深
image_row_pitch 如果host_ptr不為NULL,這個值指定影象中各行的位元組數;為0採取預設值
image_slice_pitch 如果host_ptr不為NULL,這個值指定影象中各個切片的位元組數;為0採取預設值
host_ptr 記憶體中線性佈局的影象緩衝區指標
errcode_ret 如果為非NULL,函式返回錯誤碼
使用示例:
cl_mem d_inputImage = clCreateImage2D(context, 0, &format, imageWidth, imageHeight, 0, NULL, &errNum);

4.3、影象讀寫

cl_int clEnqueueWriteImage(
       cl_command_queue commad_queue,
       cl_mem           image,
       cl_bool          blocking_read,
       const size_t     origin[3],
       const size_t     region[3],
       size_t           row_pitch
       size_t           slice_pitch,
       void            *ptr,
       cl_uint          num_events_in_wait_list
       const cl_event  *event_wait_list,
       cl_event        *event
          );

commad_queue 寫入命令將放入這個佇列
iamge 這是一個合法的影象物件
blocking_read 如果設定為CL_TRUE,則clEnqueueReadImage阻塞,直到資料讀入ptr
origin 要寫入相對影象原點的(x,y,z)整數座標,對於二維z=0
region 要寫入區域的(寬,高,深),對於二維z=1
row_pitch 影象中各行位元組數,預設為image_with*(byte_per_pixel)
slice_pitch 三維影象中各切片的位元組數image_height*row_pitch
ptr 這個指標指向源資料的宿主機記憶體
使用示例:
errNum = clEnqueueWriteImage(queue, d_inputImage, CL_FALSE, origin, region, 0, 0, inputImage, 0, NULL, NULL);

cl_int clEnqueueReadImage(
       cl_command_queue commad_queue,
       cl_mem           image,
       cl_bool          blocking_read,
       const size_t     origin[3],
       const size_t     region[3],
       size_t           row_pitch
       size_t           slice_pitch,
       void            *ptr,
       cl_uint          num_events_in_wait_list
       const cl_event  *event_wait_list,
       cl_event        *event
        );

commad_queue 讀取命令將放入這個佇列
iamge 這是一個合法的影象物件
blocking_read 如果設定為CL_TRUE,則clEnqueueReadImage阻塞,直到資料讀入ptr
origin 要讀取相對原影象原點的(x,y,z)整數座標,對於二維z=0
region 要讀取區域的(寬,高,深),對於二維z=1
row_pitch 影象中各行位元組數,預設為image_with*(byte_per_pixel)
slice_pitch 三維影象中各切片的位元組數image_height*row_pitch
ptr 這個指標指向寫入資料的宿主機記憶體
使用示例:
errNum = clEnqueueReadImage(queue, d_outputImage, CL_TRUE, origin, region, 0, 0, outputImage, 0, NULL, NULL);

4.4、篇外話

1.要實現影象類資料的使用,好像還需要宣告一個取樣器(cl_sampler),具體怎麼操作我還沒有試驗過。
2.在核心中讀寫影象好像需要使用固定的函式read_imagef()和write_imagef();具體解釋參考OpenCL Reference Pages =>OpenCL Compiler =>Built-in Functions => Image Functions =>read_imagef / write_imagef 。

相關推薦

OpenCLCNNOpenCL基礎API介紹

本篇介紹幾個OpenCL基礎API,涉及平臺、裝置等初始化必備函式 其次介紹幾個關於緩衝區操作以及工作空間劃分的API 建議閱讀參考書籍,我的推薦是《OpenCL程式設計指南》和《OpenCL異構計算》,尤其是後者從實踐出發,更是適合上手。 本章節函式使用舉

OpenCLCNNCNN從入門到使用

文中比較重要的引用了幾個篇部落格,開篇給與博主感謝 記錄我從零到實現一個具體CNN網路中最有用的知識乾貨 以細節為切入點,分享我對CNN網路的簡潔 本文致力於讓你一篇文章理解CNN的具體實現與訓練方法 涉及理論不一一追述背景,主要講解其如何應用 開源的優秀CN

JS 基礎()理解JS原型物件原型鏈

目錄: 一、什麼是原型物件和原型鏈 JavaScript 常被描述為一種基於原型的語言 (prototype-based language)——每個物件對應擁有一個原型,物件以其原型為模板、從原型繼承方法和屬性。而同時原型也是物件,它也擁有原型,並從中繼承方法

Qt入門之基礎 ( ) Qt項目建立、編譯、運行和發布過程解析

qt 5 對話 讓我 進度 qmake ctr deploy 設定 設置 轉載請註明出處:CN_Simo。 題解:   本篇內容主講Qt應用從創建到發布的整個過程,旨在幫助讀者能夠快速走進Qt的世界。   本來計劃是講解Qt源碼靜態編譯,如此的話讀者可能並不能清楚地知

圖解Python 【第十Django 基礎

aps 不同的 mage 清空 font 一個 取數 ccf pos 本節內容一覽表: Django基礎:http://www.ziqiangxuetang.com/django/django-tutorial.html 一、Django簡介 Django文

垃圾收集器內存分配策略之垃圾收集器

開啟 full gc 行處理 意義 方案 發現 特征 sea 互聯網 五、垃圾收集器 如果說收集算法是內存回收的方法論,那麽垃圾收集器就是內存回收的具體實現。由於java虛擬機規範對垃圾收集器實現沒有任何的規範因此不同的廠商,不同的版本的虛擬機所提供的垃圾收集器都有可

[知乎]老狼:深入PCIPCIe之軟體

  深入PCI與PCIe之二:軟體篇 https://zhuanlan.zhihu.com/p/26244141 我們前一篇文章( 深入PCI與PCIe之一:硬體篇 - 知乎專欄)介紹了PCI和PCIe的硬體部分。本篇主要介紹PCI和PCIe的軟體介面和UEFI對P

測開之路三十Flask基礎之錯誤重定向

文件夾 技術分享 函數 png red () direct .com static 錯誤處理,框架默認的錯誤為:not Found 可以捕獲,並自定義 準備一張自定義圖片,放在static文件夾下,並在template下創建一個html文件,引用該圖片

Python開發【第六Python基礎條件和循環

ora back strong als 重復執行 操作 enume 條件表達式 服務 目錄 一、if語句 1、功能 2、語法 單分支,單重條件判斷 多分支,多重條件判斷 if + else 多分支if + elif + else 語句小結 + 案例 三元表達式 二、whil

Python開發【第五Python基礎之2

對齊方式 dex 字符串 後退 ring lag nic 有效 func 字符串格式化 Python的字符串格式化有兩種方式: 百分號方式、format方式 百分號的方式相對來說比較老,而format方式則是比較先進的方式,企圖替換古老的方式,目前兩者並存。[PEP-310

Python開發【第四Python基礎之函數

nco pos *args 更強 三元 sequence hunk ins att 三元運算 三元運算(三目運算),是對簡單的條件語句的縮寫。 # 書寫格式 result = 值1 if 條件 else 值2 # 如果條件成立,那麽將 “值1” 賦值給result

Android 異步消息處理機制前()深入理解Message消息池

連接 guid ply 指針 cau ann 區別 就會 消息處理機制 版權聲明:本文出自汪磊的博客,轉載請務必註明出處。 上一篇中共同探討了ThreadLocal,這篇我們一起看下常提到的Message消息池到底是怎麽回事,廢話少說吧,進入正題。 對於稍有經驗的開發人員來

20165231 預備作業學習基礎和C語言基礎調查

oid clu 百度知道 保持 運行 建議 內聚 理解 加減乘除 微信文章感想 讀了婁老師微信公眾號中的文章,老師給我們的啟示首先就是要堅持,萬事開頭難,但是只要肯堅持就一定會有所成就,不管是學習還是生活方面。其中最有觸動的就是減肥了,是我三四年來一直難以完成的目標。如果可

Python成長之路【第五Python基礎之文件處理

閱讀 關註 src 文件路徑 程序 opened IT 寫入 文件操作 一、文件操作 1、介紹 計算機系統分為:計算機硬件,操作系統,應用程序三部分。 我們用python或其他語言編寫的應用程序若想要把數據永久保存下來,必須要保存於硬盤中,這就涉及到應用程序要操作硬件,

Python成長之路【第五Python基礎之裝飾器

brush urn 新功能 clas 現在 hide rom 接收 調用 一、什麽是裝飾器 裝飾:裝飾既修飾,意指為其他函數添加新功能 器:器既函數 裝飾器定義:本質就是函數,功能是為其他函數添加新功能 二、裝飾器需要遵循的原則 1、不能修改裝飾器的源代碼(開放封閉原則)

Python成長之路【第五Python基礎之模塊

module 應用程序 過程 解釋器 amp 之路 Python標準庫 pre 使用 模塊&包 模塊(module)的概念: 在計算機程序開發的過程中,隨著程序代碼越寫越多,在一個文件裏代碼就會越來越長,越來越不容易維護。為了編寫可維護的代碼,我們把很多函數分組,分

Unity UGUI 原理()Canvas Scaler 縮放核心

https://blog.csdn.net/gz_huangzl/article/details/52484611   Canvas Scaler Canvas Scaler是Unity UI系統中,控制UI元素的總體大小和畫素密度的Compoent,Canvas Scaler的縮放比例影響著

QNAP 威聯通 NAS的個人使用經驗 QTS系統各功能講解

原文網址:https://post.smzdm.com/p/87164/ 接上篇 8、NAS儲存功能的使用 儲存功能是NAS最基本的功能,簡單的說,你完全可以把它當成一塊外接硬碟,只不過它通過網路和計算機連線,而非傳統的資料線而已。 想要把資料儲存在NAS上,或者訪問自己的資料,有很

資料結構演算法 叉樹(Binary Tree)(

今天要講的是二叉查詢樹(Binary Search Tree),是一種最常用的二叉搜尋樹,支援快速查詢,刪除,插入資料。 它是如何實現的呢?,其實它依靠的它的資料結構,在樹中的任意一個節點,其左子樹的每個節點的值都小於這個節點的值,右子樹都大於這個節點的值。 接下來我們來看一下二叉樹是

資料結構演算法 叉樹(Binary Tree)(一)

好多天沒有寫過資料結構和演算法了,好了今天抽出點時間二叉樹,前面講到的都是線性表,棧,佇列等等。 今天講到的是非線性表結構--樹,首先說一下什麼是樹的概念 樹的這種資料結果挺像我們現實中的樹,這裡的每一個元素我們叫做節點,用線把相鄰的節點連線起來,然後它們就成了父子關係。 A節點是