链表的逆置、合并、排序以及插入删除

在做题之前,让我们现简单理一下顺序表,链表的概念和关系。
首先我们要知道的是顺序表和链表都属于线性表。
线性表:由n(n>=0)个相同类型数据元素组成,并且可以在任意位置进行插入和删除数据元素操作的有限序列。

顺序表:用一组地址连续的存储单元依次存储数据元素的线性结构。
这里写图片描述

单链表:它是一种链式存储的线性表,用一组地址任意的存储单元存放线性表的数据元素,称存储单元为一个节点。

其结构如下:
这里写图片描述

其中节点Node如下:
这里写图片描述

1 . 比较顺序表和链表的优缺点,说说它们分别在什么场景下使用?
要比较顺序表和链表的优缺点,首先我们要了解他们各自的特点:
顺序表:存储分配——静态分配;
    存取方式——随机存取;
    插入删除需移动元素个数——移动近一半元素。
链表: 存储分配——动态分配;
    存取方式——顺序存取;
    插入删除需移动元素个数——不移动元素,只修改指针。

a. 空间的开辟:
顺序表的实现一般是实现连续开辟一段空间(或根据需求动态连续开辟空间),然后在进行数据的增删查改(静态顺序表),所以顺序表一般是固定空间大小的;
单链表则是一次只开辟一个结点的空间,用来存储当前要保存的数据及指向下一个结点或NULL的指针,所以单链表的空间大小时动态变化的。
b. 空间的使用:
当我们不知道要存储多少数据时,用顺序表来开辟的空间如果太大,就会造成一定程度上的浪费。
用单链表是实现时,因为是每需要存储一个数据时,才开辟一个空间,虽然有非数据项的指针占空间,但相比顺序表来说,浪费不是那么明显。(因为链表每次都是开辟的位置都是随机的,那么可能会把这块空间搞得七零八碎,出现很多小的一般使用不到的碎片空间)
c. 对CPU高速缓存的影响:
顺序表的空间一般是连续开辟的,而且一次会开辟存储多个元素的空间,所以在使用顺序表时,可以一次把多个数据写入高速缓存,再写入主存,顺序表的CPU高速缓存效率更高,且CPU流水线也不会总是被打断;
单链表是每需要存储一个数据才开辟一次空间,所以每个数据存储时都要单独的写入高速缓存区,再写入主存,这样就造成了,单链表CPU高速缓存效率低,且CPU流水线会经常被打断。
  由其特点可知,顺序表的优点是便于随机存储,缺点是不便于插入删除等操作,因为插入删除一个元素需要移动其后的所有元素,但是链表不存在这个问题,链表只要改变指针就行,时间复杂度小,所以链表于顺序表恰恰相反,优点是便于插入删除等操作,缺点是随机存储没有顺序表方便。
所以,当我们需要随机存取的时候,适合使用顺序表;当需要做频繁插入删除操作时,适合使用链表。

2 . 从尾到头打印单链表(逆序打印单链表)
运用递归方法打印

//逆序打印单链表
void ReversePrint(ListNode* pList)
{
    ListNode* cur = pList;
    if (pList == NULL )
    {
        return;
    }
    ReversePrint(cur->next);
    printf("%d-- ", cur->data);
}

运行结果:

这里写图片描述

3. 删除一个无头单链表的非尾节点
因为是无头链表,所以
在删除节点时不可直接free(pos)节点,会使之前数据丢失,所以找到它的下一节点交换并保存数据,然后删除下一节点数据即可。

//删除一个无头链表非尾节点
void EraseNotTail(ListNode* pos)
{
    assert(pos&&pos->next);
    ListNode* cur;
    DataType ret;
    cur = pos->next;
    ret = pos->next->data;
    pos->next->data = pos->data;
    pos->data = ret;
    pos->next = pos->next->next;
    free(cur);
}

运行结果
这里写图片描述
4. 在无头单链表的一个非头节点前插入一个节点

void InsertNotHead(ListNode* pos,DataType x)
{
    ListNode* cur;
    cur = Buynewnode;

}
  1. 单链表实现约瑟夫环

6. 逆置/反转单链表

单链表的逆置与双向链表不同,它只有next指向,所以逆置单链表的思想如图:
这里写图片描述

首先将第一个节点变成一个单独的节点,即他的next为NULL,因为要将他作为重新排序后的最后一个节点。接下来将它后面的每一位依次插入到他的前面,做前面这个逆序链表的头节点。需注意得的是最后一个节点不能进入while循环,所以在后面将他直接插入头节点前。

  void Reverselist(ListNode** ppList)
{
    assert(ppList);
    ListNode* cur=NULL;
    ListNode* prev=NULL;
    ListNode* p = NULL;
    if ((*ppList) == NULL)
    {
        return;
    }
    cur =( *ppList)->next;
    (*ppList)->next = NULL;
    while (cur->next!=NULL)
    {
        prev = cur;
        cur = cur->next;
        prev->next = *ppList;
        *ppList = prev
    }
    cur->next = *ppList ;
    *ppList = cur;
}

运行结果:
这里写图片描述

7. 单链表排序(冒泡排序&快速排序)
冒泡排序
这里写图片描述

void BubbleSort(ListNode** ppList)
{
    assert(ppList);
    ListNode* pre;
    ListNode* cur;
    ListNode* tail = NULL;
    DataType tmp;
    if (*ppList == NULL && (*ppList)->next==NULL)
    {
        return ;
    }
    while ((*ppList)->next != tail)
    {
        pre = *ppList;
        cur = pre->next;
        while ((cur!=tail)&&(pre->next!=tail))
        {
            if (pre->data < cur->data)
            {
                tmp = cur->data;
                cur->data = pre->data;
                pre->data = tmp;
            }
            pre = pre->next;
            if (cur->next != tail)
            {
                cur = cur->next;
            }               
        }
        tail = cur;
    }
}

运行结果:
这里写图片描述

8. 合并两个有序链表,合并后依然有序

ListNode* Merge(const ListNode** p1, ListNode** p2)
{
    ListNode* first = NULL;
    ListNode* second = NULL;
    ListNode* cur = NULL;
    ListNode* cur2 = NULL;
    assert(p1);
    assert(p2);
    first = *p1;
    second = *p2;
    if (((*p1) == (*p2)) || (*p1 == NULL) || (*p2 == NULL))
    {
        return NULL;//如果两个链表都为空,或者有一个为空返回
    }
    while (second->next != NULL) //插入的链表不能为空,为空停止
    {
        while ((first->data) < (second->data))//比较两个链表的元素
        {
            if (first->next != NULL)
            {
                cur = first;//保存前一个结点,为以后插入式使用
                first = first->next;
            }
            else
            {
                break;//如果被插的那条链表为空,直接跳出去插入剩下的元素
            }
        }
        cur2 = second;//保存要插入的元素
        *p2 = (*p2)->next;//链表二向后移动
        second = *p2;
        cur2->next = first;
        cur->next = cur2;
    }
    first->next = *p2;//跳出来之后直接把剩下的元素加上去
    return *p1;
}
  1. 查找单链表的中间节点,要求只能遍历一次链表
  2. 查找单链表的倒数第k个节点,要求只能遍历一次链表
  3. 判断单链表是否带环?若带环,求环的长度?求环的入口点?并计算每个算法的时间复杂度&空间复杂度。
  4. 判断两个链表是否相交,若相交,求交点。(假设链表不带环)
  5. 判断两个链表是否相交,若相交,求交点。(假设链表可能带环)【升级版】
  6. 复杂链表的复制。一个链表的每个节点,有一个指向next指针指向下一个节点,还有一个random指针指向这个链表中的一个随机
    节点或者NULL,现在要求实现复制这个链表,返回复制后的新链表。
  7. 两个已排序单链表中相同的数据。void UnionSet(Node*l1, Node*l2);
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值