1. 程式人生 > >資料結構7.動態陣列

資料結構7.動態陣列

引言:

我們之前提到過 陣列(Array),是一種資料結構,是資料元素(elements)的集合。

陣列的特點:

//陣列的缺點:
//
//    1.一旦陣列定義,則大小固定,無法進行修改(陣列的大小)。
//    2.陣列插入和刪除的效率太低,時間複雜度O(n)。
//    
// 陣列的優點:
//    
//    1.下標訪問,速度快,時間複雜度是O(1)

陣列的定義:

//方法1
int array1[10] = {0};    

//方法2
int array2[] = {12, 23, 34, 45, 56, 67, 78};  

//方法3
int *array3 = NULL;
array3 = (int
*)malloc(sizeof(int) * 100); if(array3 != NULL){ fprintf(stderr, "the memory is full!\n"); exit(1); }

當初,我們說array1和array2都在定義時直接分配了棧上的記憶體空間;
而array3是一個int型別的指標,指向我們在堆上用malloc分配的4bytes ×100 = 400 bytes大小的空間。

普通情況下這樣陣列雖然在O(1)的時間複雜度訪問下標進行資料存取查詢,可是一般陣列定義後,大小不能夠進行動態變化,且插入刪除效率過低,時間複雜度為O(n).
當初,為了彌補這些不足,我們又學習到了連結串列。而連結串列失去了很多陣列的優點。
那怎麼樣才可以兼顧陣列和連結串列的優點呢?今天我們將要學習動態陣列。動態陣列

,是一種可以在任何時候改變大小的陣列裝置。

一、動態陣列基

動態陣列是指在編譯時不能確定陣列長度,程式在執行時需要動態分配記憶體空間的陣列。

我們要怎麼樣為陣列分配到用來儲存資料的空間呢。

NAME
       malloc, free, calloc, realloc - allocate and free dynamic memory

SYNOPSIS
       #include <stdlib.h>

       void *malloc(size_t size);
       void free(void *ptr);
       void *calloc
(size_t nmemb, size_t size); void *realloc(void *ptr, size_t size);
  1. void *malloc(size_t size);
    malloc 向系統申請分配指定size個位元組的記憶體空間。返回型別是 void* 型別。void* 表示未確定型別的指標。C,C++規定,void* 型別可以強制轉換為任何其它型別的指標。

  2. void free(void *ptr);
    釋放malloc(或calloc、realloc)函式給指標變數分配的記憶體空間的函式,釋放申請的動態記憶體。
    使用後該指標變數一定要重新指向NULL,防止野指標出現,有效規避誤操作。(另:對於free(p)這句語句,如果p 是NULL 指標,那麼free 對p 無論操作多少次都不會出問題。如果p 不是NULL 指標,那麼free 對p連續操作兩次就會導致程式執行錯誤。)

  3. void *calloc(size_t nmemb, size_t size);
    在記憶體的動態儲存區中分配n個長度為size的連續空間,函式返回一個指向分配起始地址的指標;如果分配不成功,返回NULL。
    一般使用後要使用 free(起始地址的指標) 對記憶體進行釋放,不然記憶體申請過多會影響計算機的效能,以至於得重啟電腦。如果使用過後不清零,還可以使用指標對該塊記憶體進行訪問。

  4. void *realloc(void *ptr, size_t size);
    realloc函式功能為先判斷當前的指標是否有足夠的連續空間,如果有,擴大mem_address指向的地址,並且將mem_address返回,如果空間不夠,先按照newsize指定的大小分配空間,將原有資料從頭到尾拷貝到新分配的記憶體區域,而後釋放原來mem_address所指記憶體區域,同時返回新分配的記憶體區域的首地址,即重新分配儲存器塊的地址。
  1. 我們看到用malloc()可以自己在堆上定義大小分配的空間,所以我們可以用malloc()為我們提供分配記憶體的方法。
  2. 如果已分配的記憶體不夠我們可以使用relloc()函式,再次申請記憶體。

於是,照常我們將這些需要經常使用的函式,放入工具類定義:
tools.h

#ifndef _TOOLS_H_
#define _TOOLS_H_

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


//工具類介面
void *Malloc(size_t size);
void swap(void *a, void *b, int length);
void *Realloc(void *ptr, size_t size);
#endif

tools.c

#include "tools.h"

void *Malloc(size_t size)
{
    void *result = malloc(size);
    if(result == NULL){
        fprintf(stderr, "the memory is full!\n");
        exit(1);
    }
    return result;
}

void swap(void *a, void *b, int length)
{
    void *temp = Malloc(length);

    memcpy(temp, a, length); 
    memcpy(a, b, length); 
    memcpy(b, temp, length);    

    free(temp);
}

void *Realloc(void *ptr, size_t size)
{
    void *result = realloc(ptr, size);
    if(result == NULL)
    {
        fprintf(stderr, "the memory is full !\n");
        exit(1);
    }
    return result;
}

照例可以使用迭代器:

iterator.h

#ifndef _ITERATOR_H_
#define _ITERATOR_H_

typedef struct Iterator
{
    void   *ptr;
    int   index;
    int    size;
}Iterator;

typedef Iterator iter;
/*
 *正向迭代器
 * container(list、array、stack)容器
 */

#define FOREACH(iter, container) \
    for(container->iter_head(&(iter), container); \
    (iter).ptr; \
    container->iter_next(&(iter), container))

#define foreach FOREACH

#define FOREACH_REVERSE(iter, container) \
    for(container->iter_tail(&(iter), container); \
    (iter).ptr; \
    container->iter_prev(&(iter), container))

#define foreach_reverse FOREACH_REVERSE

#endif

二、動態陣列定義

dynamic_array.h

#ifndef _ARRAY_H_
#define _ARRAY_H_

#include "iterator.h"

#define TRUE       (1)
#define FALSE      (0)
#define MODE_SIZE  (32)
#define ZERO       (0)

typedef unsigned char Boolean;
typedef struct Array Array;

struct Array
{
    void    **data;    //1.儲存實體
    int   capacity;    //2.動態陣列申請大小
    int      count;    //3.當前元素個數

    //4.拷貝函式指標
    void *(*copy)(void *src_value);
    //5.匹配函式指標
    Boolean (*match)(void *value1, void *value2);
    //6.釋放函式指標
    void (*free)(void *ptr);

    //7.頭部插入
    Boolean (*push_front)(Array *array, void *value);
    //8.尾部插入
    Boolean (*push_back)(Array *array, void *value);
    //9.頭部刪除
    Boolean (*pop_front)(Array *array);
    //10.尾部刪除
    Boolean (*pop_back)(Array *array);
    //
    //迭代器操作
    //11.指向陣列頭部的位置
    void *(*iter_head)(Iterator *iter, Array *array);
    //12.指向陣列尾部的位置
    void *(*iter_tail)(Iterator *iter, Array *array);
    //13.指向後一個元素的位置
    void *(*iter_next)(Iterator *iter, Array *array);
    //14.指向前一個元素的位置
    void *(*iter_prev)(Iterator *iter, Array *array);
};

//動態陣列介面
//1.初始化
Array *init_array(int init_size);
//2.銷燬
void destroy_array(Array **array);
//3.清空
void clean_array(Array *array);
//4.插入到指定下標的前面
Boolean array_insert_prev(Array *array,
                         int index, void *value);
//5.插入到指定下標的後面
Boolean array_insert_next(Array *array,
                         int index, void *value);
//6.得到陣列個數
int get_array_count(Array *array);
//7.得到指定下標元素
void *get_index_value(Array *array, int index);
//8.刪除指定下標元素
Boolean delete_index_value(Array *array, int index);
//9.刪除指定下標範圍的元素
Boolean delete_range_value(Array *array, int begin, int end);
//10.查詢指定元素的下標
int find_array_value(Array *array, void *value);

#endif

三、動態陣列介面實現

dynamic_array.c

#include "dynamic_array.h"
#include "tools.h"

//前插、尾插、前刪、尾刪、
static Boolean array_push_front(Array *array, void *value);
static Boolean array_push_back(Array *array, void *value);
static Boolean array_pop_front(Array *array);
static Boolean array_pop_back(Array *array);

//迭代器 頭、尾、下一個、前一個
static void *array_iter_head(Iterator *iter, Array *array);
static void *array_iter_tail(Iterator *iter, Array *array);
static void *array_iter_next(Iterator *iter, Array *array);
static void *array_iter_prev(Iterator *iter, Array *array);

//封裝陣列增長函式
static void array_grow(Array *array, int size);
static int  adjust_size(int size);

//1. 數字調整
static int  adjust_size(int size)
{
    //MODE_SIZE == 32
    size += (MODE_SIZE -1);    //100 -> 100 +31
    size /= (MODE_SIZE);       //131 -> 131 /32 == 4
    size *= (MODE_SIZE);       //4   -> 4 * 32  == 128
                               //將其size增長到離size最近的32的倍數處
    return size;
}
//2.調整陣列大小
static void array_grow(Array *array, int size)
{
    int adjust = 0;

    if(array->capacity < size)
    {
        adjust = adjust_size(size); //增長
        array->capacity = adjust;

        if(array->data != NULL)
        {
            array->data = Realloc(array->data,
                              sizeof(void *)*adjust);
        }
        else
        {
            array->data = Malloc(sizeof(void *)*adjust);
        }
    }
}

//陣列的插入刪除操作
//1.前插
static Boolean array_push_front(Array *array, void *value)
{
    return array_insert_prev(array, 0, value);
}
//2.尾插
static Boolean array_push_back(Array *array, void *value)
{
    if(array ==NULL || value == NULL)
    {
        return FALSE;
    }

    //如果陣列容量不夠,增長
    if(array->count >= array->capacity)
    {
        array_grow(array, array->count + MODE_SIZE);
    }

    array->data[array->count] = value ;
    array->count ++;

    return TRUE;
}
//3.前刪
static Boolean array_pop_front(Array *array)
{
    int i =0 ;
    void *delete = NULL;

    if(array == NULL || array->count == ZERO)
    {
        return FALSE; 
    }
    array->count -- ;

    delete = array->data[0];
    if(array->free != NULL)
    {
        array->free(delete);
    }

    while(i < array->count)
    {
        array->data[i] = array->data[i+1];
        ++i;
    }

    array->data[i] = NULL;
    return TRUE;
}
//4.尾刪
static Boolean array_pop_back(Array *array)
{
    void *delete = NULL;
    if(array == NULL || array->count == ZERO)
    {
        return FALSE; 
    }
    array->count -- ;
    delete = array->data[array->count];
    if(array->free != NULL)
    {
        array->free(delete);
    }

    array->data[array->count] = NULL;
    return TRUE;

}

//迭代器操作介面
//1. 迭代器頭
static void *array_iter_head(Iterator *iter, Array *array)
{
    if(iter == NULL || array == NULL)
    {
        return NULL;
    }
    iter->index = 0;
    iter->size = array->count;
    if(array->data == NULL || array->count == ZERO)
    {
        iter->ptr = NULL;
    }
    else
    {
        iter->ptr = array->data[0];
    }
    return iter->ptr;
}
//2. 迭代器指向尾
static void *array_iter_tail(Iterator *iter, Array *array)
{
    if(iter == NULL || array == NULL)
    {
        return NULL;
    }
    iter->index = array->count -1;
    iter->size = array->count;
    if(array->data == NULL || array->count == ZERO)
    {
        iter->ptr = NULL;
    }
    else
    {
        iter->ptr = array->data[iter->index];
    }
    return iter->ptr;
}
//3. 迭代器 只想下一個陣列元素
static void *array_iter_next(Iterator *iter, Array *array)
{
    if(iter == NULL || array == NULL)
    {
        return NULL;
    }
    iter->index ++;
    iter->size = array->count;
    if( iter->index >= iter->size)
    {
        iter->ptr = NULL;
    }
    else
    {
        iter->ptr = array->data[iter->index];
    }
    return iter->ptr;
}
//4. 迭代器 指向前一個數組元素
static void *array_iter_prev(Iterator *iter, Array *array)
{
    if(iter == NULL || array == NULL)
    {
        return NULL;
    }
    iter->index --;
    iter->size = array->count;
    if( iter->index <= ZERO)
    {
        iter->ptr = NULL;
    }
    else
    {
        iter->ptr = array->data[iter->index];
    }
    return iter->ptr;
}
//動態陣列介面
//1.動態陣列初始化
Array *init_array(int init_size)
{
    Array *array = (Array *)Malloc(sizeof(Array));
    //對控制資訊成員進行初始化
    array->count = 0;

    //陣列元素拷貝、比較、釋放指標初始化為NULL
    array->free = NULL;
    array->match = NULL;
    array->copy = NULL;

    //頭插、尾插、頭刪、尾刪
    array->push_front = array_push_front;
    array->push_back  =  array_push_back;
    array->pop_front  =  array_pop_front;
    array->pop_back   =   array_pop_back;

    //迭代器操作
    array->iter_head = array_iter_head;
    array->iter_tail = array_iter_tail;
    array->iter_next = array_iter_next;
    array->iter_prev = array_iter_prev;

    array->data = NULL;
    array->capacity = 0;

    if(init_size > 0)
    {
        array_grow(array, init_size);
    }
    return array;
}
//2.動態陣列的銷燬
void destroy_array(Array **array)
{
    //釋放陣列元素對應的空間
    //釋放data
    //釋放array
    if(array == NULL)
    {
        return ;
    }
    delete_range_value(*array, 0, get_array_count(*array));

    free(*array);
    *array = NULL;
}
//3.動態陣列清空
void clean_array(Array *array)
{
    if(array == NULL)
    {
        return ;
    }
    int i = 0 ;
    while(i < array->count)
    {
        array->data[i] =  NULL;
        ++i;
    }
}
//4.插入到指定下標的前面
Boolean array_insert_prev(Array *array, int index, void *value)
{
    int i = 0;
    if(array == NULL || value == NULL
                     || index > get_array_count(array))
    {
        return FALSE;
    }

    //如果陣列容量不夠,增長
    if(array->count+1 >= array->capacity)
    {
        array_grow(array, array->count + MODE_SIZE);
    }
    i = array->count;
    //index及以後的元素向後推移
    while(i > index)
    {
        array->data[i] = array->data[i-1];
        --i;
    }
    //插入元素
    array->count ++;
    array->data[i] = value;
    return TRUE;
}
//5.插入到指定下標的後面
Boolean array_insert_next(Array *array,
                         int index, void *value)
{
    int i = 0;
    if(array == NULL || value == NULL
                     || index > get_array_count(array))
    {
        return FALSE;
    }

    //如果陣列容量不夠,增長
    if(array->count+1 >= array->capacity)
    {
        array_grow(array, array->count + MODE_SIZE);
    }
    i = array->count;
    //index及以後的元素向後推移,從後向前防止被覆蓋
    while(i > index)
    {
        array->data[i] = array->data[i-1];
        --i;
    }
    //插入元素
    array->count ++;
    array->data[i] = value;
    return TRUE;
}
//6.得到陣列個數
int get_array_count(Array *array)
{
    if(array == NULL)
    {
        return -1;
    }
    return array->count;
}
//7.得到指定下標元素
void *get_index_value(Array *array, int index)
{
    if(array == NULL || index > get_array_count(array))
    {
        return NULL;
    }
    return array->data[index];
}
//8.刪除指定下標元素
Boolean delete_index_value(Array *array, int index)
{
    int i = 0;
    if(array == NULL || index > get_array_count(array))
    {
        return FALSE;
    }

    i = index;
    //陣列元素的移動,將其覆蓋
    while(i < array->count)
    {
        array->data[i] = array->data[i+1];
        ++i;
    }
    //刪除最後一個
    if(array->free != NULL)
    {
        free(array->data[array->count]);
    }
    array->data[array->count] = NULL;
    array->count --;
    return TRUE;
}
//9.刪除指定下標範圍的元素
Boolean delete_range_value(Array *array, int begin, int end)
{
#if 1
    int i = begin;
    int diff = end - begin + 1;
    int count = get_array_count(array);
// 0 1 2 3 4 5 6 7 8 9 
//     x x x           count:10  begin:2    end:4   diff:3
// 0 1 5 6 7 8 9 x x x
    if(array == NULL || begin > count || begin < 0 || end < 0
         || end > count || diff < 0 )
    {
        return FALSE;
    }

    //後面diff個元素向前推移
    while(i < count-diff)
    {
        array->data[i] = array->data[i+diff];
        ++i;
    }

    if(array->free != NULL)
    {
        i = array->count;
        while(i-- > count - diff)
        {
            free(array->data[i]);
            array->data[i] = NULL;
        }
    }
    array->count -= diff;
    return TRUE;
#endif
}
//10.查詢指定元素的下標
int find_array_value(Array *array, void *value)
{
    int i = 0 ;
    int count = get_array_count(array);
    //printf("array_count:%d\n",count);
    if(array == NULL || value == NULL)
    {
        return -1;
    }
    #if 1
    //這裡通過元素是否相同做比較
    while(i++ < count)
    {
        if(*((int *)(&array->data[i])) == *((int *)value))
        {
            return i;
        }
    }
    //或者通過匹配指標函式match
    #else
    for(i = 0 ; i < count; ++i)
    {
        if(array->match!=NULL)
        {
            if(array->match(array->data[i], value))
            {
                return i;
            }
        }
        else
        {
            if(array[data[i] == value)
            {
                return i;
            }
        }
    }
    return -1;
}

四、函式功能實現

檔案結構:

├── dynamic_array
├── dynamic_array.c
├── dynamic_array.h
├── iterator.h
├── main
├── main.c
├── tools.c
└── tools.h

main.c

#include <stdio.h>
#include "iterator.h"
#include "dynamic_array.h"

int main(int argc, char **argv)
{
    int i = 0;
    Array *array= init_array(30);
    int a[] = {1, 2, 3, 4, 5};
    int length = sizeof(a)/sizeof(a[0]);

    for(i = 0; i < length; ++i)
    {
        array_insert_prev(array, i, &a[i]);
    }

    for(i = 0; i < length; ++i)
    {
        array_insert_next(array, i, &a[i]);
    }

    printf("array_count:");
    printf("%d \n",get_array_count(array));

    length = get_array_count(array);
    printf("length:%d\n",length);
    printf("Array :\n");
    for(i = 0; i < length; ++i)
    {
        printf("%d ",*(int *)array->data[i]);
    }
    printf("\n");
    printf("array_index_count_2:");
    printf("%d \n",*(int *)get_index_value(array, 2));


    printf("delete_index_value(array, 2):\n");
    delete_index_value(array, 2);

    length = get_array_count(array);
    for(i = 0; i < length; ++i)
    {
        printf("%d ",*(int *)array->data[i]);
    }
    printf("\n");

    delete_range_value(array, 1, 3);
    length = get_array_count(array);
    for(i = 0; i < length; ++i)
    {
        printf("%d ",*(int *)array->data[i]);
    }
    printf("\n");
/*    foreach(iter, array)
    {
        printf("%d ",*(int *)array->data[i]);
   }
 */
    printf("find_array_value(array, ?)\n");
    printf("array[5]: %d \n",find_array_value(array, &array->data[5]));
    printf("array[3]:%d \n",find_array_value(array, &array->data[3]));
    printf("\n");

    printf("clean_array(array)\n");
    clean_array(array);
    length = get_array_count(array);
    for(i = 0; i < length; ++i)
    return 0;
}

執行結果:

root@aemonair:# cc.sh *.c 
Compiling ...
-e CC      dynamic_array.c main.c tools.c -g -lpthread
-e         Completed .
-e         Fri Jun 24 11:51:10 CST 2016

root@aemonair:~# ./dynamic_array 
array_count:10 
length:10
Array :
1 2 3 4 5 1 2 3 4 5 
array_index_count_2:3 
delete_index_value(array, 2):
1 2 4 5 1 2 3 4 5 
1 1 2 3 4 5 
find_array_value(array, ?)
array_count:6
array[5]: 5 
array_count:6
array[3]:3 

clean_array(array)

總結

至此,我們已經完成了動態陣列的實現。
我們可以從中看到,C語言可以用來實現各種各樣的資料結構,同時,作為動態陣列,我們多次使用malloc和free等函式,需要對記憶體的申請釋放特別注意。
我們在其中所使用的各種通用思想,以及迭代器的方法,需要再深入理解。通過我們的新鮮知識對舊的知識點進行進一步拓展和提升。