数据结构 --- 单链表

在面对链表的题目时,大部分人(包括我自己)都是很怵的,因为这必将面临指针。

笔者今天实现了一些关于链表操作的题目。

首先,定义单链表中的节点:

struct ListNode
{
    int val;
    ListNode *next;
    ListNode(int x):val(x),next(NULL){}
};

//求链表长度

int length(ListNode *head)
{
    if(head == NULL) return 0;
    int len = 0;
    while(head)
    {
        ++len;
        head = head->next;
    }
    return len;
}

//创建单链表,使用的是函数模板,针对不同的输入
template
   
   
    
    
ListNode *buildList(T beg, T end)
{
    if(beg ==  end) return NULL;
    ListNode *head = NULL, *prev = NULL;
    for(; beg != end ; ++beg)
    {
        ListNode *node = new ListNode(*beg);
        if(head == NULL)
        {
            head = node;
            prev = head;
        }
        else
        {
            prev->next = node;
            prev = node;
        }
    }
    return head;
}

//删除单链表,并且释放内存!!这是很重要的一步,否则将会导致内存泄露
void destoryList(ListNode *head)
{
    if(head == NULL) return;
    ListNode *next = head;
    while(head)
    {
        next  = head->next;
        delete head;
        head = next;
    }
}

//打印单链表
void printList(ListNode *head)
{
    if(head == NULL) cout << "EMPTY..." << endl;
    while(head)
    {
        cout << head->val << " " ;
        head = head->next;
    }
    cout << endl;
}

   
   

注:上述的删除链表函数是必须的,否则内存泄露将会是最大的问题。

[updated]:上述删除链表的操作是有问题的,传入的参数必须是指针的指针或者是引用。或者返回head。因为需要将head指针置为空。这利用传值调用是办不到的。


接下来,定义链表的一些操作:

1 返回倒数第K个节点:

//有待更新

2 逆置链表;依次取出原链表中的元素,将其插入到新链表的头部.

//reverse!!!
ListNode *reverse(ListNode *head)
{
    if(head == NULL || head->next == NULL) return head;
    //在此处我本想直接将new_head置为head,再循环head后面的元素,但是会出现死循环。
    //在逆置过程中,因为后续的head的元素都是插入到new_head的最前面,也就意味着,第一个插入的元素的next指针是不会被更改的。
    //因为在新的链表里面,该元素是最后一个元素,那么这样就会使得新的链表的new_head指针不指向空,直接导致死循环
    ListNode *new_head = NULL;
    ListNode *temp = NULL, *new_temp = NULL;

    while(head)
    {
        temp = head->next;
        new_temp = new_head;
        new_head = head;
        new_head->next = new_temp;
        head = temp;
    }
    return new_head;
}

注意,在逆置单链表的过程中,我犯了一个错误,见上述代码注释:

3 判断链表是否是回文:


//reverse the last half and compare
//将链表的后半部分逆置,依次比较链表的元素。
bool isPalindrome(ListNode *head)
{
    cout << "enter isPalindrome..." << endl;
    printList(head);

    if(head == NULL || head->next == NULL)
        return true;
    ListNode *fast = head->next;
    ListNode *slow = head;
    while(fast && fast->next)
    {
        fast = fast->next->next;
        slow = slow->next;
    }
    ListNode *L = head;
    ListNode *R = slow->next;
    slow->next = NULL;
    R = reverse(R);

    cout << " L :" ;
    printList(L);
    cout << " R :" ;
    printList(R);

    while( L && R)
    {
        if(L->val != R->val)
        {
            return false;
        }
        L = L->next;
        R = R->next;
    }
    //如果链表元素个数是偶数,那么直接返回true.
    //如果链表元素个数是奇数,那么还需要判断是否在其中一个链表中只剩下一个元素即可。
    if( ( L == NULL && R == NULL) || (L == NULL && R->next == NULL) || ( R == NULL && L->next == NULL))
        return true;
    return false;
}

当然,判断回文还有其他方法,不过我只想到这个,就直接实现了。


4 将链表的元素根据某个pivot进行partition,这可以用作链表快排的函数。



//注意一定要将less_head, grt_head链表的末尾置为空!!!
ListNode* Partition(ListNode *head, const int pivot)
{
    if(head == NULL || head->next == NULL) return NULL;

    ListNode *less_head = NULL , *less_temp = NULL;
    ListNode *grt_head = NULL, *grt_temp = NULL;
    ListNode *next = NULL;
    while(head)
    {
        next = head->next;
        if(head->val >= pivot)
        {
            if(grt_head == NULL)
            {
                grt_head = head;
                grt_temp = head;
            }
            else
            {
                grt_temp->next = head;
                grt_temp = head;
            }
        }
        else
        {
            if(less_head == NULL)
            {
                less_head = head;
                less_temp = head;
            }
            else
            {
                less_temp->next = head;
                less_temp = head;
            }
        }
        head =  next;
    }
    if(less_head)
    {
        less_temp->next = grt_head;
        head = less_head;
        grt_temp->next = NULL;
    }
    else
    {
        head = grt_head;
        grt_temp->next = NULL;
    }
    return head;
}

5 将两个链表中的元素相加,分为两个版本,第一个是:

高位在表头,那么先将链表长度补成一致,再进行递归调用即可。


//将链表补齐,在表头添加元素0
ListNode *paddle(ListNode *head, int len)
{
    if(len == 0) return head;
    ListNode *temp = NULL;
    while(len-- > 0)
    {
        ListNode *node = new ListNode(0);
        //head must not be empty
        temp = head;
        head = node;
        head->next = temp;
    }
    return head;
}

//递归调用此函数
//carry应该是引用,因为在算当前位的时候,需要知道低位的进位,但是此时函数已经有了返回值,因此使用引用来传递carry这个返回值。
ListNode *addTwoEqualLenList(ListNode *lhs, ListNode *rhs, int &carry)
{
    //必然同时为空
    if(lhs == NULL || rhs == NULL) return NULL;
    //先计算后面的位数,再计算当前位
    ListNode *node = addTwoEqualLenList(lhs->next, rhs->next, carry);
    int sum = lhs->val + rhs->val + carry;
    carry = sum/10;
    sum = sum%10;
    ListNode *head = new ListNode(sum);
    head->next = node;

    return head;
}

//相加
ListNode *addTwoListReverse(ListNode *lhs, ListNode *rhs)
{
    if(lhs == NULL && rhs == NULL) return NULL;
    if(lhs == NULL) return rhs;
    if(rhs == NULL) return lhs;
    int len1 = length(lhs);
    int len2 = length(rhs);
    //补齐元素
    if(len1 < len2)
    {
        lhs = paddle(lhs, len2-len1);
    }
    else
    {
        rhs = paddle(rhs, len1-len2);
    }
    cout << "after paddle..." << endl;
    printList(lhs);
    printList(rhs);

    assert(length(lhs) == length(rhs));

    int carry = 0;
    ListNode *ret = addTwoEqualLenList(lhs, rhs, carry);
    cout << "DEBUG : ";
    printList(ret);

    //处理最高位的进位
    if(carry)
    {
        ListNode *node = new ListNode(1);
        ListNode *temp = ret;
        ret = node;
        ret->next = temp;
    }
    
    destoryList(lhs);
    destoryList(rhs);

    return ret;
}

低位在表头。直接相加即可,只需要处理最后元素可能的进位!!



//add two list and return a new List
ListNode *addTwoList(ListNode *lhs, ListNode *rhs)
{
    cout << "lhs : " ;printList(lhs);
    cout << "rhs : " ;printList(rhs);

    if(lhs == NULL && rhs == NULL) return NULL;
    if(lhs == NULL) return rhs;
    if(rhs == NULL) return lhs;

    ListNode *ret = NULL;
    ListNode *lhs_temp = NULL, *rhs_temp = NULL, *ret_temp = NULL;
    int sum = 0 , carry = 0;
    while(lhs && rhs)
    {
        sum = lhs->val + rhs->val + carry;
        carry = sum/10;
        sum = sum%10;
        ListNode *node = new ListNode(sum);
        if(ret == NULL)
        {
            ret = node;
            ret_temp = node;
        }
        else
        {
            ret_temp->next = node;
            ret_temp = node;
        }
        rhs = rhs->next;
        lhs = lhs->next;
    }

    //which is empty
    if(lhs != NULL) swap(lhs,rhs);

    while(rhs)
    {
        sum = rhs->val + carry;
        carry = sum/10;
        sum = sum%10;
        ListNode *node = new ListNode(sum);
        //ret must not be empty
        ret_temp->next = node;
        ret_temp = ret_temp->next;
        rhs = rhs->next;
    }
    if(carry)
    {
        ListNode *node = new ListNode(1);
        ret_temp->next = node;
    }
    return ret;
}

6 判断一个链表中是否有环,如果有,返回环的起点,如果没有,直接返回,

首先,判断一个链表是否有环,典型的追及问题,一个快指针fast,每次走两步,慢指针slow,每次走一步。

如果存在环,则慢指针一定会在进入环的第一圈内就与快指针相遇。如果不是第一圈,也就意味着,慢指针在第一圈时,会被快指针追上

并超过,显然是不可能的。如果在点i处超越,那么此时慢指针在i处,快指针在i+1处,那么前一步,快指针在i-1处,慢指针也在i-1处,这

与“指针不相遇”假设矛盾。

ListNode* isCycle(ListNode *head)
{
    if(head == NULL ) return NULL;
    ListNode *fast = head->next;
    ListNode *slow = head;
    while(fast && fast->next)
    {
        if(fast == slow && fast != NULL)
        {
            return slow;
        }
        fast = fast->next->next;
        slow = slow->next;
    }
    return NULL;
}

ListNode *findCycle(ListNode *head)
{
    if(head == NULL) return NULL;

    ListNode *temp = isCycle(head);
    if(temp == NULL) return NULL;

    //注意,在链表中的追及问题,fast指针首先应该指向slow的下一步。
    //这样就能保证以后每一步,两者之间的差距均增加1
    //也就是两者之间差距依次为,1,2,3,4,5...
    ListNode *fast = temp->next;
    ListNode *slow = head;
    while(fast != slow)
    {
        fast = fast->next;
        slow = slow->next;
    }
    return fast;
}

上述程序有些繁琐,特别是在处理 fast与slow的关系时,下面的代码比较简单也比较直观

ListNode* isCycle(ListNode *head)
{
    if(head == NULL ) return NULL;
    ListNode *fast = head;
    ListNode *slow = head;

    while(fast && fast->next)
    {
        fast = fast->next->next;
        slow = slow->next;
        //fast与slow是否相等的判断必须在移动之后,因为刚开始两者都是指向head的!!!
        if(fast == slow)
            break;
    }
    if(fast == NULL || fast->next == NULL)
        return NULL;
    return slow;
}

ListNode *findCycle(ListNode *head)
{
    if(head == NULL) return NULL;

    ListNode *temp = isCycle(head);
    if(temp == NULL) return NULL;

    ListNode *fast = temp;
    ListNode *slow = head;

    while(fast != slow)
    {
        fast = fast->next;
        slow = slow->next;
    }
    return fast;
}


PS:所有代码的易错点均在代码注释中~~~~




  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值