17_7_14:逆置單鏈表+查找單鏈表的倒數第K個節點+非常規方法實現Add函數

1.【基礎題】–逆置/反轉單鏈表+查找單鏈表的倒數第k個節點,要求只能遍歷一次鏈表

2.【附加題】–實現一個Add函數,讓兩個數相加,但是不能使用+、-、*、/等四則運算符。ps:也不能用++、–等等

**

1,基礎題:

**

/*
注:本次代碼處理的是不帶環的單鏈表
*/
#include <stdio.h>
#include <stdlib.h>

typedef struct ListNode
{
    int _val;
    struct ListNode* _pNext;
}Node, *PNode;

/*
逆置 / 反轉單鏈表:
思路:可以利用三個指針來標記三個連續節點。
1,將頭結點的指針置爲NULL
2,將頭結點後面的_pNext在循環中改變指向:
    第一個指針不變,逆置中間指針的指向,保留第三個指針的指向,作爲往後遍歷鏈表的路徑。
    這樣遍歷鏈表,每次逆置中間節點的指向,通過第三個節點往後遍歷,將三個節點指針整體往後移一個節點。
    最終改變整個鏈表。
通過以上思路,將鏈表分爲三種情況:
1,鏈表爲空。
2,鏈表只含有1個節點。
3,鏈表有2個及以上節點。
*/
PNode ReverseList(PNode pHead)
{ 
    PNode pPre = NULL;  //第一個指針,指向連續節點中的第一個節點,最終成爲新的頭結點指針。
    PNode pCur = NULL;  //第二個指針,指向連續節點中的第二個節點,修改_pNext指向的指針。
    PNode pTail = NULL; //第三個指針,指向連續節點中的第三個節點,保持與單鏈表連接的指針。

    //鏈表爲空的情況
    if (NULL == pHead)
        return NULL;

    //鏈表只有一個節點的情況
    if (NULL == pHead->_pNext)
        return pHead;

    //鏈表有2個及以上節點的情況

    pPre = pHead;
    pCur = pPre->_pNext;
    pTail = pCur->_pNext;

    //處理pHead的_pNext指向
    pHead->_pNext = NULL;

    //處理頭結點之後的節點_pNext指向
    while (pCur)
    {
        pCur->_pNext = pPre;
        pPre = pCur;
        pCur = pTail;

        if (pTail) //當該條件不成立時,表示pTail爲NULL,下一次循環中pCur也NULL,將會跳出循環。
            pTail = pTail->_pNext;
    }

    //此時pPre指向的是新的頭結點
    return pPre;
}

/*
查找單鏈表的倒數第k個節點,要求只能遍歷一次鏈表:

思路:通過兩個指針,一個前指針,一個後指針。
1,兩個指針,初始都是指向單鏈表的頭結點。
2,前指針先走K步。然後和後指針一起往後走。
3,直到前指針指向NULL。

注意,根據K的大小與單鏈表中元素個數的比較。可以分爲:
1,K的值大於單鏈表中元素個數。
2,K的值小於等於單鏈表中元素個數。
3,需要注意K爲0的情況
但是,鏈表中元素個數,必須通過遍歷之後,才能知曉。
所以,可以在前指針往後走K步的過程中(即K-1步及之前),同時判斷前指針是否爲NULL。
如果前指針爲NULL,表示單鏈表元素個數小於K。否則大於等於K。
*/
PNode Find_the_penultimate_K_node(PNode pHead, size_t k)
{
    //使用左、右來標識前、後指針。pHead是否爲NULL,不影響程序邏輯。
    PNode pLeft = pHead;
    PNode pRigth = pHead;

    if (!k)
        return NULL;

    //前指針先往後走K步
    while (k && pLeft)
    {
        pLeft = pLeft->_pNext;
        --k;
    }

    if (k) //k>0,pLeft=NULL.表示元素個數小於K
        return NULL;

    //走到這一步,說明單鏈表元素個數大於等於K,然後,前後指針開始一起往後走。直到前指針爲NULL
    while (pLeft)
    {
        pLeft = pLeft->_pNext;
        pRigth = pRigth->_pNext;
    }

    //返回倒數第K個節點指針。
    return pRigth;
}

//打印單鏈表
void PrintList(PNode pHead)
{
    while (pHead)
    {
        printf("%d->", pHead->_val);
        pHead = pHead->_pNext;
    }

    printf("NULL\n");
}

int main()
{
    int i = 0;
    Node node[10] = {0}; //建立10個node用來做實驗
    PNode pHead = &node[0]; //指向頭結點
    PNode pTemp = NULL; //指向倒數第K個節點
    int k = 0;

    //給node賦值
    for (; i < 10; ++i)
        node[i]._val = i;
    //構成一個單鏈表
    for (i = 0; i < 9; ++i)
        node[i]._pNext = &node[i + 1];
    node[9]._pNext = NULL; //注意,最後一個節點指向NULL

    PrintList(pHead);   //打印舊的單鏈表

    pTemp = Find_the_penultimate_K_node(pHead, 5); //尋找倒數第K個節點
    printf("Find the penultimate K node is %d\n", pTemp->_val);

    pHead = ReverseList(pHead); //逆置單鏈表
    PrintList(pHead); //打印新的單鏈表

    system("pause");
    return 0;
}

**

2,附加題

**
(1)利用兩數相加,從後往前進位的思想

思路:
1,0和1可以表示某一位上的數值。(對於sum來說)
2,0和1還可以表示兩種狀態,即序列中某一位的狀態(不需要進位與需要進位)。(對於carry來說)
3,a^b運算的結果是:二進制序列,不進位,只相加的結果sum。(0^0=0,1^0=1,1^1=0)
4,a&b運算,然後左移一位,的結果是:二進制序列中哪些位是需要進位carry。(0&0=0,1&0=0,1&1=1,然後將序列左移1位)
5,通過sum與carry帶入3,4中的a與b,可以從後往前不斷進位,直到carry爲0,表示沒有需要進位的位。

/*
//遞歸版本
int Add_2(int a, int b)
{
    if (0==b) 
        return a;
    else
    {
        int sum = a ^ b; // 各位相加,不計進位
        int carry = (a & b) << 1; // 記下進位
        Add_2(sum, carry); // 求sum和carry的和
    }
}
*/
/**/
//迭代版本
int Add_2(int a, int b)
{
    while (b)
    {
        int sum = a ^ b;
        int carry = (a & b) << 1;

        a = sum;
        b = carry;
    }
    return a;
}

(2)利用數組來計算。
這種辦法非常巧妙
思路:
1,數組通過下標的偏移,可以計算出基於首元素地址的某一元素位置,從而訪問該元素。
那麼我們可以利用編譯器的這種行爲,來讓編譯器自動幫我們進行加法運算。
2,32位下, int值與指針的sizeof大小都是4字節。char的大小爲1字節。
所以,可以將兩數a和b中。a強轉爲char*類型地址c。這樣,b用來表示下標,計算c[b]時,是以1字節爲單位計算偏移的。
*/

int Add(int a, int b)
{
    char *c = (char *)a; //將a表示的值,看做是地址c。並且類型要是char*。因爲sizeof(char)=1

    return (int)&c[b]; //&a[b]-->a+sizeof(char)*b表示地址。通過(int)強轉爲int值
}

由於這種方法是從別處瞭解,本人第一次見,所以需要仔細分析分析
1,只是用於32平臺?
是的,因爲通過int值與指針值在32位平臺下,都是4個字節,大小一樣。所以在解析時,可以通過(char *) 、(int)來相互強轉,而不會,多讀取或者少讀取字節數據。
2,b爲負數時,可滿足情況?
可以滿足。因爲,通過數組下標來訪問元素的本質,就是基於某個地址的偏移。可以有正偏移(往後偏移),也可以有負偏移(往前偏移)。

測試用例:

    int a[5] = {1,2,3,4,5};
    int* p = NULL;
    p = &a[3];
    printf("a[-1] = %d\n", p[-1]);//可以查看p[-1]的值是否爲3

3,a爲負數時,可滿足情況嗎?
可以滿足。因爲,在將a值轉化爲地址c時,會將a的補碼序列,看做無符號數。我擔心的是,在將有符號數化作無符號數,計算時,會不會出問題。
後來,終於想到,在兩個數計算時,其實是他們的補碼序列在計算。這樣就脫離了正負的約束,只是在讀取時,會根據是否有符號來決定是否判斷補碼的標識位。
所以,不管a的正負,甚至是b的正負,在計算a(有符號)+b(有符號)與計算c(無符號)+b(無符號)結果的二進制補碼序列相同。但是(int)會把無符號數強轉爲有符號的。這樣在讀取c+b結果表示的二進制補碼序列時,是以有符號數的規則來讀取的,這與讀取a+b的結果相同。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章