数据结构:线性表之单链表

本文介绍了单链表的基本概念及特点,详细分析了链表与数组的不同,并提供了单链表的C语言实现代码,包括节点创建、插入、删除等核心操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

单链表实现

链表特点:

  1. 链表是指一个数据元素含有一个或多个指向另外一个数据元素的指针或引用。称这样的元素为节点。一条链上的节点,多形象。
    链表结构的元素在内存中是分散存放的。不需要连续的存储空间。空间利用率较高。可以动态增加长度。
  2. 而由于元素是通过指针联系在一起的,所以插入和删除元素较快,只需要修改插入和删除位置元素的指向即可。这些是链表的优势。
  3. 链表的劣势是对元素的访问只能遍历寻找(不包含特殊指向如头尾)。查找和访问元素较慢。下面看一组对比:

数组和链表对比

操作数组链表
按位置读下标访问O(1)遍历O(n)
元素定位O(n)O(n)
插入元素O(n)O(n)
删除O(n)O(n)
元素数量限制初始化参数内存容量
数据冗余O(n)

注: 定位、插入和删除两种操作之所以复杂度都是O(n)是因为都涉及到了遍历元素,在涉及到所有元素的算法里复杂度最少是O(n)因为至少要读一遍元素。

虽然两种结构插入删除近似时间复杂度O相同,但是我觉的链表的效率是比较高的,分析如下:
一、 数组和链表的遍历是不同的,数组是要遍历移动元素(需要读写内存读i-1存到i)以挪出空位或填补空位,而链表是O(n)的读内存操作(定位元素), O(1)更改指针操作。
所以对内存的操作少一半。
二、对于双链表删除,当我们持有待删除元素指针时就可以直接操作待删除元素指向来删除元素.这时删除操作的时间复杂度是O(n).插入元素也大同小异.

另外链表的遍历也有很多优化时间性能的方法.

demo代码如下:

注:代码分析在最后,修改于2017/6/9.

//定义node
typedef struct person2{
    char name[10];
    int age;
    struct person2 *next;

}person2;

//用于封装链表
typedef struct linkedList{
    person2 *head;
    person2 *end;
    int length;
}linkedList;

//初始化node
person2 *newP2(char *name,int age){
    //动态分配内存
    person2 *p = (person2 *)malloc(sizeof(person2));
    p->age = age;
    strcpy(p->name,name);
    p->next = NULL;
    return p;
}

//建立空表
linkedList *newList(){

    //建立头结点
    linkedList *head = (linkedList *)malloc(sizeof(linkedList));

    //头结点分配实体
    head->head = newP2("",-1);

    //尾节点指向头结点
    head->end = head->head;

    head->length = 0;
    return head;
}



//add element in end
void addEnd(linkedList *head, person2 *p){

    //使链表的最后一个节点(end)的next指针指向新节点。
    head->end->next = p;
    //更新尾节点
    head->end = p;

    head->length++;

}

//根据index序号读表元素,返回数据域data(index序号按写入顺序,从0开始)
person2 *readL(linkedList *head, int index){
    if(head->length > index && index >= 0 ){
        person2 *phead = head->head->next;
        int j = 0;

        while (phead != NULL && j < index){
            phead = phead->next;
            j++;
        }
        if(j == index){
            return phead;
        }
    }

    return NULL;
}

//定位元素 返回下标(从0开始)
int locatep2(linkedList *head, person2 *p){
    int i;
    person2 *phead = head->head->next;

    for(i = 0; phead != NULL; i++){
        if(compareP2(phead,p)){
           return i;
        }
        phead = phead->next;
    }
    return -1;
}

//person 比较器
int compareP2(person2 *p1,person2 *p2){
    if(p1 == p2){
        return 1;
    }

    return (p1->age == p2->age && !strcmp(p1->name,p2->name));
}

//insert element before index
void insertlink(linkedList *head,person2 *per,int index){


        person2 *p;

        //得到要插入位置的前一个元素
        if(index == 0){
            p = head->head;
        }else if(index == head->length){
            addEnd(head,per);
            return ;

        }else{
            p = readL(head,index-1);
        }

        //insert element
        if(p != NULL){
            per->next = p->next;
            p->next = per;

            head->length++;
        }else{
            printf("index not find");
            exit(1);
        }




}

//delete element by index
void myRemove2(linkedList *head,int index){

      person2 *p1;

      //如果要删除第一个元素
        if(index == 0 ){
            //指向第一个节点的head
            person2 *phead = head->head;

            //待删除元素p1指向首节点
            p1 = phead->next;

            //使head指向下下一个节点
            phead->next = phead->next->next;
        }
        else if(index == head->length-1){

            p1 = head->end;
            head->end = readL(head,index);

        }
        else{    //不是首尾节点的情况
             //得到要插入位置的节点
            person2 *p3 = readL(head,index-1);


        //delete element
            if(p3 == NULL){
                printf("index not find");
                exit(1);
            }
            p1 = p3->next;
            p3->next = p1->next;

        }
    free(p1);//一定要释放节点空间
    head->length--;
}

//print element
void linkPrint(linkedList *head){
    int length = head->length;
    person2 *begin = head->head->next;
    printf("***********length = : %d ************\n",length);

    for(;begin != NULL; begin = begin->next){
        printf("name: %s,age: %d\n",begin->name,begin->age);
    }

}

void linkTest(){
    person2 *p1;
    //printf("hello world\n");

    //初始化
    linkedList *head = newList();
    addEnd(head,newP2("HH",0));
    addEnd(head,newP2("HH",1));
    addEnd(head,newP2("HH",2));
    addEnd(head,newP2("HH",3));
    addEnd(head,newP2("HH",4));
    addEnd(head,newP2("HH",5));

    linkPrint(head);

    //测试按下标查找
    p1 = readL(head,2);
    if(p1 != NULL)
    printf("found person 2 :name = %s,age = %d\n",p1->name,p1->age);

   //test 按元素查找位置
    printf("found person index: index = %d\n",locatep2(head,newP2("HH",2)));

    //test insert element
    printf("insert element 0 ,4\n");
    insertlink(head,newP2("new",66),0);
    insertlink(head,newP2("new",66),4);
    linkPrint(head);

    //test delete element
    printf("delete element 0 ,4\n");
    myRemove2(head, 0);
    myRemove2(head, 4);
    linkPrint(head);
}

结构体分析

对于节点,我们只需让其携带必要的数据即可,上面的例子中node结构代码如下:

struct person{
            dataType data;
            person *next;
};

但是这样写有一个问题,怎么表示head、end、length。或者在哪里封装?办法是建一个专门的头结点结构如下:

        struct linkList{
            person *head;//head->next指向链表第一个元素
            person *end;//指向链表最后一个元素
            int length;//记录表长
        };

**对于封装头结点结构体,每个链表只需要一个**,

这样封装头结点可以实现addFirst 和 addLast,这需要在建空表的时候创建两个实体person节点来表示队首和队尾,这样每条链表就有两个节点冗余,但还能接受。
本例只创建了一个head实体,来实现在队尾添加节点。
改进版本的实现见下一篇 双向链表。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值