数据结构与算法 - 链表

链表

题型1:数组和链表的区别是什么?

数组和链表的区别主要表现在以下几个方面:

1)逻辑结构。数组必须事先定义固定的长度,不能适应数据动态地增减。当数组中插入、删除数据项时,需要移动其他数据项。而链表采用动态分配内存的形式实现,可以适应数据动态第增减的情况,需要时可以用new/malloc分配内存空间,不需要时使用delete/free将已分配的空间释放,插入和删除元素不需要移动数据项。

2)内存结构。数组从栈中分配空间,链表从堆中分配空间。

3)数组中的数据在内存中是顺序存储的,而链表是随机存储的。数组的随机访问效率很高,可以直接定位,但插入、删除操作的效率比较低。链表的插入、删除操作不需要移动元素。

4)链表不存在越界的问题,数组有越界的问题。

题型2:对单链表进行排序

方法1:使用冒泡排序

方法2:使用直接插入排序

方法3:使用归并排序

当我们需要对链表进行排序时,由于不能对它的元素进行随机访问,所以更适合使用归并排序,大名鼎鼎的快速排序用到链表上,效率也很低,原因还是在于不能对链表中的元素进行随机访问,同理,采用堆排序更是不可能的事情。

算法具体实现时需要一个指向头节点(链表的第一个节点,链表中不包含额外的一个节点来作头节点)的指针,这是因为在算法实现的时候,不大可能第一个节点正好就是所有元素中最小的一个,则链表的头节点会改变,因此我们需要一个指向头节点的指针来存储不断变化的头节点。

算法思想:

MergeSort(headRef)

1) If head is NULL or there is only one element in the Linked List
    then return.
2) Else divide the linked list into two halves.
      FrontBackSplit(head, &a, &b); /* a and b are two halves */
3) Sort the two halves a and b.
      MergeSort(a);
      MergeSort(b);
4) Merge the sorted a and b (using SortedMerge() discussed here)
   and update the head pointer using headRef.
     *headRef = SortedMerge(a, b);
代码示例:

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

/*Link list node*/
struct node
{
    int data;
    struct node* next;
};  

/*function prototype */
struct node* SortedMerge(struct node* a, struct node* b);
void FrontBackSplit(struct node* source, struct node** frontRef, struct node** backRef);  

/*sorts the linked list by changing next pointers(not data) */
void MergeSort(struct node** headRef)
{
    struct node* head = *headRef;
    struct node* a;
    struct node* b;  

    /*base case-- length 0 or 1 */
    if((head == NULL) || (head->next == NULL))
    {
        return;
    }  

    /*Split head into ‘a‘ and ‘b‘ sublists */
    FrontBackSplit(head, &a, &b);  

    /*Recursively sort the sublists */
    MergeSort(&a);
    MergeSort(&b);  

    /* answer = merge the two sorted lists together */
    *headRef = SortedMerge(a, b);
}  

struct node* SortedMerge(struct node* a, struct node* b)
{
    struct node* result = NULL;  

    /* Base cases */
    if(a == NULL)
        return (b);
    else if(b == NULL)
        return (a);  

    /* Pick either a or b recur */
    if(a->data <= b->data)
    {
        result = a;
        result->next = SortedMerge(a->next, b);
    }
    else
    {
        result = b;
        result->next = SortedMerge(a, b->next);
    }
    return (result);
}  

/* UTILITY FUNCTIONS */
/* Split the nodes of the given list into front and back halves,
    and return the two lists using the references parameters.
    If the length is odd, the extra node shold go in the front list.
    Uses the fast/slow pointer strategy. */
void FrontBackSplit(struct node* source, struct node** frontRef, struct node** backRef)
{
    struct node* fast;
    struct node* slow;  

    if(source == NULL || source->next == NULL)
    {
        *frontRef = source;
        *backRef = NULL;
    }
    else
    {
        slow = source;
        fast = source->next;  

        /* Advance ‘fast‘ two nodes, and advance ‘slow‘ one node */
        while(fast != NULL)
        {
            fast = fast->next;
            if( fast != NULL )
            {
                slow = slow->next;
                fast = fast->next;
            }
        }  

        *frontRef = source;
        *backRef = slow->next;
        slow->next = NULL;
    }
}  

/*Function to print nodes in a given linked list*/
void printList(struct node* node)
{
    while( node != NULL )
    {
        printf("%d  ", node->data);
        node = node->next;
    }
}  

/* Function to insert a node at the begining of the linked list*/
void push(struct node** head_ref, int new_data)
{
    /*allocate node*/
    struct node* new_node = (struct node*)malloc(sizeof(struct node));  

    /*put in the data*/
    new_node->data = new_data;  

    /*link the old list off the new node*/
    new_node->next = (*head_ref);  

    /*move the head to point to the new node*/
    (*head_ref) = new_node;
}  

/* Drier program to test above functions*/
int main()
{
    /* Start with the empty list */
    struct node* res = NULL;
    struct node* a = NULL;  

    /* Let us create a unsorted linked lists to test the functions
       Created lists shall be a: 2->3->20->5->10->15 */
    push(&a, 15);
    push(&a, 10);
    push(&a, 5);
    push(&a, 20);
    push(&a, 3);
    push(&a, 2);   

    /* Sort the above created Linked List */
    MergeSort(&a);  

    printf("\n Sorted Linked List is: \n");
    printList(a);             

    return 0;
}  

时间复杂度为O(nLogn)。
貌似MergeSort的时间复杂度为O(nLogn),Split的时间复杂度也为O(nLogn)?当然了,总的时间复杂度还是O(nLogn),但是肯定没有对数组进行归并排序快。

原文地址:https://www.cnblogs.com/alantu2018/p/8460824.html

时间: 2024-10-11 23:06:32

数据结构与算法 - 链表的相关文章

数据结构与算法-链表的基本操作---ShinPans

//链表操作:建立.插入.删除.查找.倒置.删除等基本操作 #include<stdio.h> #include<stdlib.h> typedef  struct LNode {       int data;       structLNode *next; }LNode,*Llist; LNode *creat_head();//创建一个空表 void creat_list(LNode *,int);//创建一个长度为n的线性链表 void insert_list(LNode

python数据结构与算法——链表

具体的数据结构可以参考下面的这两篇博客: python 数据结构之单链表的实现: http://www.cnblogs.com/yupeng/p/3413763.html python 数据结构之双向链表的实现: http://www.cnblogs.com/yupeng/p/3413800.html 我这里只实现了单链表的类型,代码也相对精简一点: 先构造关于节点的类: 1 class Node: 2 def __init__(self,data=None,next=None): 3 self

数据结构和算法--链表一之单向链表的简单实现

链表在我们java中也是一种基础的数据结构,可以理解成是一种和数组同级的数组结构,正如我们所知,在我们使用这集合ArrayList和LinkedList的时候,总会学习底层数组实现的ArrayList和双向链表实现的LinkedList的区别.在这里,我们将要讲说的是单向链表的简单实现,让我们体会一下链表在实现增删改查的时候是怎么样的一个操作,在和前边涉及到的数组的增删改查进行对比,得到我们学习的结论,数组的增删效率低于链表结构,查改效率高于链表结构! 什么叫做单向链表,我们可以理解为一个一个节

数据结构与算法 —— 链表linked list(02)

我们继续来看链表的第二道题,来自于leetcode: 两数相加 给定两个非空链表来代表两个非负整数,位数按照逆序方式存储,它们的每个节点只存储单个数字.将这两数相加会返回一个新的链表. 你可以假设除了数字 0 之外,这两个数字都不会以零开头. 示例: 输入:(2 -> 4 -> 3) + (5 -> 6 -> 4) 输出:7 -> 0 -> 8 原因:342 + 465 = 807 分析: 因为是位数按照逆序方式存储,所以链表的前置节点是地位,直接循环链表相加,生成新的

数据结构和算法-链表

链表分类 单向链表 双向链表 优势: 删除某个节点更加高效, 可以快速找到前驱节点 可以方便的在某个节点前插入元素 循环链表 当要处理的数据具有环形结构的时候, 适合循环链表. 如约瑟夫环问题 双向循环链表 数组的缺点是大小固定, 一旦声明长度就要占用连续的内存空间, 当空间不够用时更换更大的空间, 此时就需要将原数组的所有数据迁移过去, 比较费时. 链表则可以动态扩容. 数组在查询上可以更快, 链表在插入和删除上更快, 为了结合数组和链表的优点, 有同时使用的情况, 比如一个网站的用户注册,

数据结构与算法-链表查找倒数第K个值

查找链表中倒数第k个结点题目:输入一个单向链表,输出该链表中倒数第k个结点.链表的倒数第0个结点为链表的尾指针.链表结点定义如下: struct ListNode { int m_nKey; ListNode* m_pNext; }; int FindCoundDownInList(pListNode head,int num) { pListNode p1,p2; p1=p2=head; while(num-->0 && p1!=NULL) p1=p1->m_pNext; i

数据结构与算法-链表反转

1.输入一个链表的头结点,从尾到头反过来输出每个结点的值.链表结点定义如下:struct ListNode{      int       m_nKey;      ListNode* m_pNext;};分析:这是一道很有意思的面试题.该题以及它的变体经常出现在各大公司的面试.笔试题中.看到这道题后,第一反应是从头到尾输出比较简单.于是很自然地想到把链表中链接结点的指针反转过来,改变链表的方向.然后就可以从头到尾输出了. 接下来的想法是从头到尾遍历链表,每经过一个结点的时候,把该结点放到一个栈

数据结构与算法-链表就地逆置

链表操作,单链表就地逆置 void Inverse(LinkList &L) { LNode *p, *q; p = L->next; /*记录第一个结点地址*/ L->next = NULL; /*把链表设置成空表*/ while (p != NULL) /*依次按头插法将各结点插入,就实现了逆置*/ { q = p; /*用q记录待插入结点地址*/ p = p->next; /*用p记录待插入结点的后继结点地址*/*/ q->next = L->next; /*将

javascript数据结构与算法--链表

链表与数组的区别?  1. 定义: 数组又叫做顺序表,顺序表是在内存中开辟一段连续的空间来存储数据,数组可以处理一组数据类型相同的数据,但不允许动态定义数组的大小,即在使用数组之前必须确定数组的大小.而在实际应用中,用户使用数组之前有时无法准确确定数组的大小,只能将数组定义成足够大小,这样数组中有些空间可能不被使用,从而造成内存空间的浪费. 链表是一种常见的数据组织形式,它采用动态分配内存的形式实现.链表是靠指针来连接多块不连续的的空间,在逻辑上形成一片连续的空间来存储数据.需要时可以用new分