17_7_14:逆置单链表+查找单链表的倒数第K个节点+非常规方法实现Add函数

来源:互联网 发布:巴哥犬价格知乎 编辑:程序博客网 时间:2024/06/07 06:52

1.【基础题】–逆置/反转单链表+查找单链表的倒数第k个节点,要求只能遍历一次链表

2.【附加题】–实现一个Add函数,让两个数相加,但是不能使用+、-、*、/等四则运算符。ps:也不能用++、–等等

**

1,基础题:

**

/*注:本次代码处理的是不带环的单链表*/#include <stdio.h>#include <stdlib.h>typedef struct ListNode{    int _val;    struct ListNode* _pNext;}Node, *PNode;/*逆置 / 反转单链表:思路:可以利用三个指针来标记三个连续节点。1,将头结点的指针置为NULL2,将头结点后面的_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的结果相同。

阅读全文
1 0