1.数据结构之单链表

数据结构之链表

链表的简介

数据结构是编程中对数据处理的一种常见的容器,它们的发明总是从简单到复杂的一个过程,在C语言的编程中我们首先接触了基本数据类型,包括定点数(char、short、int、long)、浮点数(float、double)、指针类型。接着了解到了数组(相同数据类型的集合),结构体(不同数据类型的集合)。再往后随着数据规模的增大以及数据之间的复杂关系,又开发出了链表、栈、队列、树(二叉树、字典树、B树等)、图等数结构,使我们对数据的处理能力进一步加强,所以掌握好数据结构非常的重要,本次博客将会着重介绍相关数据结构的实现。首先让我们从最简单的链表开始数据结构之旅。

链表与数组

首先让我们来介绍下链表与数组之间的有缺点,关于数组我们有以下几种定义方式:

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

int *array3 = NULL;

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

上述是我们定义数组的三种方式,他们对内存的占用位置是不同的,简单的叙述下:

array2和array3是我们的整型数组,它们存在于进程的栈上(后面会对内存的分布进行详细的介绍),而array3则和其他两个略有不同,array3被定义为一个int类型的指针,也就是说array3存储的是一个地址,我们在malloc函数调用中其实是在堆内存中分配了(4bytes * 100 = 400bytes)大小的空间,并且把该内存块的第一个字节的地址存在了array3中,这样相当于array3虽然在栈上,但是它所指向的空间是在堆上存放的。

不管上述的数组是如何定义的,数组都会满足以下几个特点:

1.数组在物理上还是逻辑上都是连续存储的,其中的元素都是可以通过首地址加偏移量进行定位的。
2.使用数组对元素进行查找,时间复杂度非常低,在O(1)的时间复杂度可以完成查找。
3.使用数组进行插入和删除的时候就不那么乐观了,如果被删除或插入的元素在数组的末位,O(1)的时间复杂度也可以完成。但是绝大多数情况下插入和删除的元素都是在数组的中间进行,这样的话时间复杂度将会上升到O(n)。
4.最关键的一点,一旦数组定义完成后,其大小是不可以更改的。这样就要求使用者能够精确的估计数据规模,但现实环境中很可能会造成数组过大或过小而引发浪费和不足的窘境。

由此我们可以发现数组有着很多的不足,还好我们发明了更加灵活的数据结构,链表应该是其中的一个代表(尽管它也不是那么的完美),链表由相同结构的链表节点构成,关于它们是如何能够链接在一起的,我们需要去关注链表节点的结构:
这里写图片描述

我们可以看到链表节点主要分为两大部分:数据域和链域。数据域是我们最要存储的内容,而链域则代表着一种指针,这个指针指向类型为链表节点,这样每一个节点就可以记录下下一个节点的位置,从而把原本毫不相干的链表节点串接起来。

关于链表节点的结构体定义如下所示:

#define TRUE      (1)
#define FALSE     (0)

typedef unsigned char Boolean;

typedef struct List_node
{
    int               data;   //数据域
    struct List_node *next;    //指针域
}List_node;

typedef List_node *List_head;

为了方便链表操作的实现我们重定义了Boolean类型作为逻辑上的真或假。

链表的操作也是在.h文件中进行声明的,我们叫这样的声明为接口,如下是单链表的接口声明:

List_head        init_list(void)                      ;    //链表的初始化
void             destroy_list(List_head *head)        ;    //链表的删除
Boolean          push_front(List_head head, int value);    //头部插入
Boolean          push_back(List_head head, int value) ;    //尾部插入
Boolean          pop_front(List_head head)            ;    //头部删除
Boolean          pop_back(List_head head)             ;    //尾部删除
Boolean          delete_list_node(List_head head,
                                  List_node *node)    ;    //删除某个指针的值
Boolean          find_value_node(List_head head, 
                          int value, List_node **node);    //找到指定值的地址
void             show_list(List_head head)            ;    //显示链表信息    
void             sort_list_ascend(List_head head)     ;    //链表的升序排序
void             sort_list_descend(List_head head)    ;    //链表的降序排序
int              list_length(List_head head)          ;    //链表的长度

设计完链表的接口,我们就需要全力以赴的实现这些接口,就好像我们在设计完产品的配料表和使用说明书之后就需要专心致志的设计具体的产品,所以关于链表接口的具体实现我们放在list.c中实现,如下所示:

List_head        init_list(void)    //链表的初始化
{
    List_head head = NULL;

    head = buy_node();

    return head;
}

void             destroy_list(List_head *head)    //链表的删除
{
     List_node *p = NULL;
     List_node *q = NULL;

     // List_node **
     if(head == NULL || *head == NULL){
        return ;
     }   

     p = *head;
     while(p != NULL){
         q = p;
         p = p->next;
         free(q);
     }
     *head = NULL;
}

Boolean          push_front(List_head head, int value)    //头部插入
{
    List_node *node = NULL;

    if(head == NULL){
        return FALSE;
    }

    //购买节点并进行赋值
    node = buy_node();
    node->data = value;

  //  printf("adfadf");
    //头部插入节点
    //                NULL->next;
    node->next = head->next;
    head->next = node;

    return TRUE;
}

Boolean          push_back(List_head head, int value)     //尾部插入
{
    List_node *node = NULL;
    List_node *p_node = NULL;

    if(head == NULL){
        return FALSE;
    }

    node = buy_node();
    node->data = value;    

    p_node = head;
    //1.找到末尾
    while(p_node->next != NULL){
        p_node = p_node->next;
    }
    //2.插入到末尾
    p_node->next = node;

    return TRUE;
}

Boolean          pop_front(List_head head)    //头部删除
{
    List_node *p_node = NULL;

    if(head == NULL || head->next == NULL){
        //链表为空或者没有有效节点,头部删除失败
        return FALSE;
    }

    p_node = head->next; 
    head->next = head->next->next;
    free(p_node);

    return TRUE;
}

Boolean          pop_back(List_head head)    //尾部删除
{
    List_node *p_node = NULL;

    if(head == NULL || head->next == NULL){
        return FALSE;
    }

    p_node = head;
    //找到最后一个节点并且删除
    while(p_node->next->next != NULL){
        p_node = p_node->next;
    }
    free(p_node->next);
    p_node->next = NULL;
    return TRUE;
}


//talk  
Boolean          delete_list_node(List_head head,
                                  List_node *node)    //删除某个指针的值
{
    List_node *p_node = NULL;

    if(head == NULL || head->next == NULL || node == NULL){
        return FALSE;
    }

    p_node = head;

    while(p_node->next != node){
        p_node = p_node->next;
        if(p_node == NULL){    //已经遍历完整个链表也没有找到指定的节点
            break ;
        }
    }
    if(p_node == NULL){   //没找到,返回删除失败
        return FALSE;
    }
    //找到并且删除
    p_node->next = node->next;
    free(node);    
    return TRUE;
}

Boolean          find_value_node(List_head head, 
                          int value, List_node **node)    //找到指定值的地址
{
    List_node *p_node = NULL;

    if(head == NULL || head->next == NULL || node == NULL){
         return FALSE;
    }    

    p_node = head->next;
    while(p_node != NULL){
        if(p_node->data == value){
            *node = p_node;
            return TRUE;
        }
        p_node = p_node->next;
    }

    return FALSE;    
}

void             show_list(List_head head)    //显示链表信息    
{
    List_node *p_node = NULL;

    if(head == NULL){
        return ;
    }

    for(p_node = head->next; p_node != NULL; p_node = p_node->next){
        printf("%d ", p_node->data);
    }
    printf("\n");
}

/*
 *    int i = 0;
 *    int j = 0;
 *      
 *    for(i = 0; i < length - 1; ++i){
 *        for(j = i + 1; j < length; ++j){
 *            if(array[i] > array[j]){
 *                 swap(&array[i], &array[j], sizeof(array[i]));
 *            }
 *        }
 *    }
 *
 * */

#define POINTER_SIZE (sizeof(void *))

#define data_size(p_node) (((char *)((p_node) + 1) - (POINTER_SIZE)) - ((char *)(p_node)))

void             sort_list_ascend(List_head head)    //链表的升序排序
{
    List_node *p_node = NULL;
    List_node *q_node = NULL;

    //如果链表为空,没有元素或者只有一个元素,不进行排序操作
    if(head == NULL || head->next == NULL 
    || head->next->next == NULL){
        return ;
    }

    for(p_node = head->next; p_node != NULL; p_node = p_node->next){
        for(q_node = p_node->next; q_node != NULL; q_node = q_node->next){
            if(p_node->data > q_node->data){
                //只交换数据域
                swap(p_node, q_node, data_size(p_node));
            }
        }
    }

}
void             sort_list_descend(List_head head)    ;    //链表的降序排序

int list_length(List_head head)   //链表的长度
{
    List_node *p_node = NULL;
    int length = 0;

    if(head == NULL){
        return length;
    }
    p_node = head->next;

    while(p_node != NULL){
        length++;
        p_node = p_node->next;
    }
    return length;
}

static 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);
}

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



static List_node *buy_node(void)    //生成一个链表节点
{
    List_node *result = NULL;

    result = (List_node *)Malloc(sizeof(List_node));
    bzero(result, sizeof(List_node));   
    //  int data;
    //  struct List_node *next;

    return result;
}

设计完链表的操作后我们需要对这个功能进行测试,测试代码如下所示:

int main(int argc, char **argv)
{
    List_head head = NULL;
    int i = 0;

    head = init_list();   //链表的初始化
    for(i = 0; i < 10; ++i){
        push_front(head, rand() % 100);
    }   

   // push_back(head, 10); 
   // push_back(head, 20);
   // push_back(head, 30); 
   // push_back(head, 40); 
   // push_back(head, 50); 
   // push_back(head, 60); 

    show_list(head);

    pop_front(head);
    pop_back(head);

    show_list(head); 
   // List_node *find = NULL;

   // Boolean ok = find_value_node(head, 30, &find);

   // if(ok == TRUE){
   //     printf("the %d value found\n", find->data);
   // }

    sort_list_ascend(head);
    show_list(head); 

    destroy_list(&head);    //链表的销毁
    return 0;
}

上述就是我们关于链表的基本操作,虽然非常简单(确实它的功能十分单一,操作不是那么的完善,而且与数据域的类型息息相关),但是清晰的描述出一个链表应该具有的操作。在后续的章节中我们将会介绍更加实用的链表操作,敬请期待。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值