您的位置:首页 > 其它

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

2017-07-14 20:39 399 查看
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的结果相同。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐