数据结构--单链表

最近复习数据结构,加强下自己的基础。在复习中遇到的问题在这里做下笔记。
单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。
单链表的定义:

typedef struct LNode{
    int data;               // data 中存在结点的数据域
    struct LNode *next;     // 指向后继结点的指针
}LNode;                     // 定义单链表结点类型

单链表的构造方法(尾插法和头插法)
(1)尾插法

void CreateListR(LNode *&C, int a[], int n){
    LNode *s, *r; //s 用来指向新申请结点, r 始终指向C的终端结点
    int i;
    C = (LNode *)malloc(sizeof(LNode *)); //申请C的头结点空间
    C -> next = NULL;  
    r = C; //r 指向 C 的头结点,因为此时 C 的头结点就是 C 的终端结点
    for (i=0;i<n;i++){
        s = (LNode *)malloc(sizeof(LNode *)); //s指向新申请结点
        s -> data = a[i]; 
        r -> next = s; //r 接纳新结点
        r = r -> next; //r 指向终端结点,以便接纳下一个到来的结点
    }
    r -> next = NULL; //所有元素都装入了链表 C 中,将 C 的终端结点的指针域置为NULL
}

尾插法

(2)头插法

void CreateListH(LNode *&C, int a[], int n){
    LNode *s;
    int i;
    C = (LNode *)malloc(sizeof(LNode *)); //申请C的头结点空间
    C -> next = NULL;
    for (i=0;i<n;i++){
        s = (LNode *)malloc(sizeof(LNode *)); //s指向新申请结点
        s -> data = a[i]; 
        s -> next = C -> next; //s 所指向新结点的指针域 next 指向 C 中的开始结点
        C -> next = s ; //头结点的指针域 next 指向 s 结点, 使得 s 结点成为了新的开始结点
    }
}

头插法

链表删除( 删除 p->next )

q = p -> next;
p -> next = p -> next -> next;
free(q);

链表删除

链表的插法(头插法和尾插法)和删除是所有链表知识的基础知识,很多操作都是由着三个基本操作组成。

下面介绍一个链表的综合题目(leetCode上面的)
给定两个非负整数(个位整数)的单链表,将两个单链表中对应元素相加的到的值保存到一个单链表中

输入: [2, 4, 3]
[5, 6, 4]
输出: [7, 0 ,8]

题目分析:(根据题目描述和输入输出实例)每个单链表中元素的值相加,如果小于10,则得到的值即为所求的值,如果相加的值大于10,那么所要求的值为个位上的数,同时十位上的1要加到下次运算中。
思想分析:可以创建一个单链表,让其长度为输入链表中最大的长度。因为单链表没有提供length的属性,那么只有通过变量才能得到最大的长度,这样会加大运行的负担,因此在遍历单链表的时候,判断单链表是否为空,如果一个单链表为空,另一个还没有为空,那么为空的那个单链表往后的值可以认为是0,这样就不会影响最终的计算,一次遍历就可以达到最大的长度。需要注意的是,如果遍历完后,最后一次的得到的值大于10,那么需要在最后再添加一个结点,来保存得到的十位数。
代码实现(C++)

#include <iostream>
using namespace std;

// Definition for singly-linked list.
struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};

//使用模板定义一个函数getArrayLen,该函数将返回数组array的长度
template <class T>
int getArrayLen(T& array)  {
    return (sizeof(array) / sizeof(array[0]));
}

class Solution {
public:
    /**
     *  用尾插法创建一个没有头结点的链表
     */
    ListNode* createListE(int array[], int n){
        ListNode *r, *s;
        ListNode* list = (ListNode *)malloc(sizeof(ListNode *));
        list->next = NULL;
        r = list;
        for (int i = 0; i < n; ++i) {
            s = (ListNode *)malloc(sizeof(ListNode *));
            s -> val = array[i];
            r -> next = s;
            r = r->next;
        }
        r->next = NULL;
        return list->next;
    }

    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {//链表不含头结点
        int flag = 0; //定义标示符,初始化为0,链表值相加大于10为1,否则为0
        ListNode* p = l1;
        ListNode* q = l2;
        ListNode* l3 = (ListNode*)malloc(sizeof(ListNode*)); //申请l3的头结点空间(可以创建带头结点的链表,然后返回l3->next, 去除头结点即可)
        l3 -> next = NULL;
        ListNode* r = l3; //r 指向 l3 的始终最后一个结点

        int valp, valq; //p,q链表对应位置的值
        while (p != NULL || q != NULL){ // 一个不为空,就继续(得到最大的长度)
            valp = (p==NULL?0:p->val); // p为空的话,对应值为0;否则为p链表的值
            valq = (q==NULL?0:q->val);

            ListNode* s = (ListNode*)malloc(sizeof(ListNode*)); //s指向新申请结点
            if (valp + valq + flag < 10){ //小于10的情况
                s -> val = (valp + valq + flag);
                flag = 0; // 小于10设为0,不影响下次的计算
            }else{ //大于10的情况
                s -> val = (valp + valq + flag) % 10; //取个位的值
                flag = 1; // 大于10设为1,下次计算加1
            }
            //链表尾插法
            r -> next = s;
            r = r -> next;

            if (p != NULL) p = p -> next; //p不为空的话,向后移动一个位置
            if (q != NULL) q = q -> next;
        }

        if (flag == 1 ){ //最后一次结果大于10的情况
            ListNode* s = (ListNode*)malloc(sizeof(ListNode*)); //再次申请新结点
            //将新结点用尾插法插入到l3链表中
            s -> val = flag;
            r->next = s;
            r = r->next;
        }
        r -> next = NULL; //所有元素都装入了链表 l3 中,将 l3 的终端结点的指针域置为NULL
        return l3->next; //返回去除头结点的 l3 链表
    }
};

int main(int argc, const char * argv[]) {
    //测试用例
    int a[] = {2,4,3};
    int b[] = {5,6,4};

//    int a[] = {3};
//    int b[] = {7,6};
//
//    int a[] = {5};
//    int b[] = {5};
//
//    int a[] = {3, 8};
//    int b[] = {2};

    //计算数组的长度
    int alength = getArrayLen(a);
    int blength = getArrayLen(b);

    Solution s;
    //尾插法得到无头结点的单链表
    ListNode* l1 = s.createListE(a, alength);
    ListNode* l2 = s.createListE(b, blength);

    //计算两个单链表的和
    ListNode* l3 = s.addTwoNumbers(l1, l2);
    ListNode* r = l3;
    while (r!=NULL) {
        cout<<r->val<<",";
        r = r->next;
    }
    cout<<endl;

    return 0;
}

例题2:判断一个链表是否有环
思路:定义两个指针,一快一慢,当两个指针相遇,则证明有环,否则没有环。
代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
   //找到第一次相遇的节点(如果有环,返回相遇的节点,没有环,返回NULL)
    bool cycleList(ListNode *head){
        ListNode *pSlow = head;//慢指针
        ListNode *pFast = head;//快指针
        while (pFast != NULL && pFast -> next != NULL){
            pFast = pFast -> next -> next;
            pSlow = pSlow -> next;
            if (pFast == pSlow) return true;//有环,返回true
        }
        return false;//无环 返回false
    }

例题3:如果一个链表有环,找出环的入口
分析:同样定义2个指针,然后将其中一个指针向后移动环的长度n,然后两个指针以相同的速度向前移动。当第二个指针指向环的入口结点时,后面的那个指针已经围绕环走了一圈,回到了入口结点,两个指针相遇。
求环的长度,可以先找出环中任意的一个结点,然后从这个结点出发,一边向后移动,一边计数,再次返回这个结点的时候,就可以得到环的长度了。
代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
     //找到第一次相遇的节点(如果有环,返回相遇的节点,没有环,返回NULL)
    ListNode *meetingNode(ListNode *head){
        ListNode *pFast = head;//快指针
        ListNode *pSlow = head;//慢指针
        while(pFast != NULL && pFast -> next != NULL){
            pFast = pFast -> next -> next;
            pSlow = pSlow -> next;
            if (pFast == pSlow) return pFast;//有环,返回相遇的节点
        }
        return NULL;//无环 返回NULL
    }

    ListNode *detectCycle(ListNode *head) {
        ListNode *meetingLN = meetingNode(head);
        if (meetingLN == NULL) return NULL;
        //计算环的长度,找到第一次的公共顶点,然后再遍历一圈,即可确定长度
        ListNode *pHeadNode = meetingLN;
        int count = 1;
        while (pHeadNode -> next != meetingLN){
            pHeadNode = pHeadNode -> next;
            ++count;
        }
        //创建2个指针,一个指向头节点,一个向后移动环的长度,两个指针相遇的地方就是环的入口
        pHeadNode = head;
        ListNode *pLastNode = head;
        while(count){
            pLastNode = pLastNode -> next;
            --count;
        }
        while(pHeadNode != pLastNode){
            pHeadNode = pHeadNode -> next;
            pLastNode = pLastNode -> next;
        }
        return pLastNode;
    }
不积跬步,无以至千里;不积小流,无以成江海。
原文地址:https://www.cnblogs.com/xiaocai-ios/p/7779800.html