【数据结构】顺序表和单链表的区别

little fairy 的第x篇博客。

顺序表和链表都是常见的线性结构。

顺序表和链表最基本的差异为:顺序表,逻辑相邻,物理也相邻(可以直接访问其前驱和后继)。链表,逻辑相邻,物理不相邻(需要通过辅助信息才能访问其前驱和后继)。

顺序表:储存在连续空间中。

(1)结构体//本例为不定长的顺序表。定长的顺序表相当于一个给定长度的数组,储存数据个数有限。

typedef struct 
{
    int *elem;//指向动态内存的指针
    int length;//有效数据个数    //用于判断是否为满表
    int listsize;//总单元个数       //若有效数据个数==总单元个数,则表满,若想继续往其中添加数据,则需要对顺序表进行扩容
}DSeqList,*PDSeqList;

2)可以通过下标访问元素,时间复杂度为O(1)。

eg:ps->elem[pos];//ps为传入的参数名(PDSeqList ps),pos为所要访问的位置

3)进行插入和删除操作时,即需要修改已有结构时,需要同时对后续数据进行相应处理。(改变了部分数据的地址)

//判断顺序表是否为满

static bool IsFull(PDSeqList ps)
{
    return GetLength(ps) == ps->listsize;
}

//对顺序表进行扩容,扩大为原来的二倍

static void IncDSeqlist(PDSeqList ps)
{
    ps->listsize *= 2;
    ps->elem = (int *)realloc(ps->elem,ps->listsize*sizeof(int));//用realloc进行扩容,保存已有数据并增容。
}

//将val插入在ps表中的pos位置
bool Insert(PDSeqList ps,int val,int pos)
{
    assert(ps != NULL);   //断言

    if(pos < 0 || pos > ps->length)  //判断需要插入的pos位置是否合法。pos必须在顺序表已有位置上,不能小于0,也不能大于顺序表的有效位置。
    {
        return false;
    }
    if(IsFull(ps))   //如果当前顺序表已经满了,则需要对其进行扩容后进行相应操作。
    {
        IncDSeqlist(ps);
    }

    for(int i = ps->length;i >= pos;i--)  //需要将pos及pos之后的数据向后移一位,使pos位置空余,后插入数据。
    {
        ps->elem[i+1] = ps->elem[i];
    }

    ps->elem[pos] = val;
    ps->length ++;   //数据插入或删除后,要对其有效长度进行相应处理。

    return true;
}
(4)逆置顺序表时只需将前后相对位置交换数据。

单链表:可以储存在连续空间中,也可以在不连续的空间中。

(1)结构体
typedef struct Node   //本例以单链表
{
    int data;     //数据
    struct Node *next;//后继指针   //储存后一个节点的地址
}Node,*List;    //List=Node*

(2)只能通过后继指针访问节点,每次访问都要遍历链表。时间复杂度为O(n);

for(Node *p = plist,int i = 0;p->next != NULL && i<pos;p = p->next;i++);

(3)进行插入和删除操作时,即需要修改已有结构时,只需要对插入位置进行相应处理,不改变其他数据的地址。

//头插,将数据插入到链表的第一个位置  时间复杂度为O(1)
bool Insert_head(List plist,int val)
{
    assert(plist != NULL);
    
    Node *p = (Node *)malloc(sizeof(Node));    //动态申请内存以保存新插入的结点
    p->data = val;

    p->next = plist->next;              //连接新节点与原有链表插入位置的后续结点
    plist->next = p;                  //断开原有链表,与新节点相连接。    ***必须先连接再断开。

    return true;
}

如果先断开再插入,则plist->next被断开,即没有办法找到plist的后继。

尾插需要找到最后一个结点,然后在节点后进行插入,需要遍历链表时间复杂度为O(n)。

4)逆置单链表。利用头插完成逆置

void Reverse(List plist)
{
    assert(plist != NULL);

    Node *p = plist->next;  //定义一个节点保存第一个数据节点,以新节点来遍历全部数据节点
    plist->next = NULL;   //将链表置空

    while(p != NULL)      //新节点不为空时,进入循环
    {        
        Node *q = p->next;  //每次都以旧节点的后继为新节点,以便便利后续链表

        p->next = plist->next;  //将旧节点以头插方式插入链表,以完成链表逆置
        plist->next = p;

        p = q;
    }
}

注意:

1、顺序表插入删除时间复杂度均为O(n),但其在尾部进行操作时时间复杂度为O(1),通过下标方式访问节点,时间复杂度为O(1)。当应用更多为查询操作时,使用顺序表。

2、顺序表要防止其数据溢出,需要进行判满、扩容等操作。gcc扩容为两倍,微软STL为1.5倍。

3、单链表插入删除时间复杂度为O(n),但其在头部进行操作时时间复杂度为O(1),通过后继指针访问节点,时间复杂度为O(n)。当应用更多为在头部进行插入删除操作时,用单链表。

4、单链表不需要进行判满操作,每次进行插入操作时要动态申请内存,删除时要释放删除的节点内存,防止内存泄漏。

5、顺序表的清空和销毁相同,清空数据域即可。

6、单链表的清空只需释放数据域内存,销毁则需要对头节点进行销毁。

猜你喜欢

转载自blog.csdn.net/ifiwere/article/details/88075883