数据结构【线性表-链表】

线性表的链式存储结构:

为了表示数据元素ai和其后继元素ai+1之间的逻辑关系,对ai来说需存储其本身信息和后继元素的信息(存储位置)。这两部分组成ai的存储映像,称为结点(Node),它包含两个域:数据域和指针域。n个结点链结在一起,就组成线性表的链式存储结构。
在这里插入图片描述
我们首先来看一下最简单也是最常用的单链表。前面我们已经了解到,链表中我们把每个内存块叫做链表的一个结点。为了将每个结点连接到一起,每个结点不仅存储数据,而且还需要记录下一个结点的地址。我们把这个记录下一个结点的指针称为后继指针next。单链表是单方向顺序的一个线性表。其中有两个结点比较特殊,分别是第一个和最后一个结点。我们通常把第一个结点叫做头结点,最后一个结点叫做尾结点,头结点用来记录链表的基地址,这样我们就可以遍历得到整个结点,尾结点是最后一个结点,它的指针指向一个空地址NULL,这样我们通过判断后继指针next是不是NULL来确定某个结点是不是尾结点。
1)单向链表

using namespace std;
struct Node
{
	int value;
	Node *next;
}*LinkedList,LNode;

接下来我们来看下双向链表,单链表只有一个指针,每个结点都只有一个后继指针next指向下一个结点的地址。而双向链表,顾名思义,它有两个方向,所以每个结点不仅有一个指向下一个结点的后继指针next,还有一个指向前一个结点的前驱结点prev。与单链表相比,双向链表的每个结点还需要存储指向前一个结点的指针,所以,同样多的数据,双向链表比单链表需要更多的存储空间。两个指针虽然比较浪费空间,但是双向链表可以双向遍历,灵活性更高。
2)双向链表

struct DNode
{
    int value;       /* Data field */
    struct DNode *prior;  /* Pointer field */
    struct DNode *next;   /* Pointer field */
}DNode;

一、单向链表的ADT定义

1.1 创建链表
使用尾插法创建单链表

 //尾插法创建单链表、链表长度为n;
void CreatLinkedList(LinkedList &L,int n)  
{
    L = (LinkedList)malloc(sizeof(LNode));  //初始化;
    L->next = NULL;
    L->data = 0;
    LinkedList Tail = L;    //尾指针;
    cout<<"Enter "<<n<<" number(s)"<<endl;
    for(int i = 0 ; i < n; i++)
    {
        LinkedList Temp = (LinkedList)malloc(sizeof(LNode));
        cin>>Temp->data;
        Tail->next = Temp;
        Tail = Temp;
        Temp = NULL;
        L->data++;  //计数;
    }
    Tail->next = NULL;
}

1.2 查找
从头结点开始,逐个查找(后移)并计数,直到第i个为止。
算法的平均时间复杂度为T(n) = O(n)

//查找链表中第一个数据为data的节点,如果找到就返回节点指针,否则返回空指针NULL
Node findInList(LinkedList lst, int data) {
    Node tmp = lst;
    while (tmp->next != NULL) {
        if (tmp->data == data) {
            return tmp;
        }
        tmp = tmp->next;
    }
    return NULL;
}

在这里插入图片描述
1.3 插入
在指定位置插入节点

	// 在链表最前面插入一个节点,插入完成后,新插入的节点为链表的新的头结点
    void addHead(LinkedList* head, int val) {
        LinkedList* newNode = new Node();
        newNode->value = val;
        newNode->next = head->next;
    }

    // 在链表最后面添加一个节点
    void addTail(LinkedList* head, int val) {
        LinkedList* newNode = new Node();
        newNode->value = val;
        newNode->next = nullptr;
        LinkedList* cur = head;
        while(cur->next != nullptr){
            cur = cur->next;
        }
        cur->next = newNode;
    }

    // 在链表的某个位置插入一个节点
    void addIndex(LinkedList* head, int index, int val) {
        LinkedNode* newNode = new Node();
        LinkedNode* cur = _dummyHead;
        while(index--) {
            cur = cur->next;
        }
        newNode->next = cur->next;
        cur->next = newNode;
    }

1.4 删除
删除结点

LinkedList* removeElements(LinkedList* head, int val) {
        //删除头节点
        while((head != NULL)&&(head->value == val)){ 
            Node* temp = head;
            head = head->next;
            delete temp;
        }
        //删除非头节点
        Node*  cur = head;  //头节点不动
        //不是空链表且不是尾部节点
        while((cur != NULL)&&(cur->next != NULL)){
        	//如果下一个节点的值等于目标值,删除
            if(cur->next->val == val){ 
                Node*  temp = cur->next; //
                cur->next = cur->next->next;
                delete temp; 
            }
            else{
                cur = cur->next;
            }
        }
        return head;

1.5 反转链表

    Node* reverseList(ListNode* head) {
    	//初始化一个空指针的节点
        Node* pre = new Node();  
        pre = nullptr;
        ListNode* cur = new Node();
        cur = head;
        Node* temp;
        while(cur != nullptr){
            temp = cur->next; 
            cur->next = pre;
            pre = cur;
            cur = temp;
        }
        return pre;
    }

1.6 链表的合并

有序表的合并,用链表实现:
在这里插入图片描述

在这里插入图片描述

Node* mergeList(Node* head1, Node* head2)
	Node* p = head1; // 指向第一个链表的指针
	Node* q = head2; // 指向第二个链表的指针
	Node* dummy = new Node(); // 创建一个虚拟节点
	Node* r = dummy; // 指向新链表的指针
	while (p != nullptr && q != nullptr) {
	    if (p->val < q->val) {
	        r->next = p; // 将第一个链表中的节点插入到新链表中
	        p = p->next; // 移动指针到下一个节点
	    } else {
	        r->next = q; // 将第二个链表中的节点插入到新链表中
	        q = q->next; // 移动指针到下一个节点
	    }
	    r = r->next; // 移动指针到新链表的最后一个节点
	}
	if (p != nullptr) {
	    r->next = p; // 将第一个链表中剩余的节点插入到新链表中
	} else {
	    r->next = q; // 将第二个链表中剩余的节点插入到新链表中
	}
	head = dummy->next; // 将新链表头指针指向第一个节点
	return head;

1.7 链表的长度

int getListSize(LinkedList* head){
	Node* p = head; // 指向链表头节点的指针
	int len = 0; // 链表长度
	while (p != nullptr) {
	    len++;
	    p = p->next; // 移动指针到下一个节点
	}
	cout << "链表的长度为:" << len << endl;
	return len;
}

二、链表的优缺点

链表的优点:
1)进行插入和删除元素的操作不需要移动其余元素,效率高,复杂度为O(1);
2)链表不要求连续空间,空间利用效率高
链表的缺点:
1)查找元素和搜索元素的效率低,最快情况为O(1),平均情况为O(N)

因此对于经常插入和删除的操作,数据结构采用 链表或者使用二叉搜索树。
在这里插入图片描述

参考资料:数据结构与算法基础课程-王卓老师

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值