前言
顺序表是用一组地址连续的存储单元来保存数据的,所以它具有随机存取的特点。即查找快速,但是做插入或删除动作是,需要移动大量元素,效率较低。
链表是线性表的链式存储结构,它相比于顺序表,在插入和删除元素时,效率要高很多。
每个数据单元有两部分组成,一个是数据域,存储数据值;另一个是指针域,指向下一个数据单元。这样的数据单元叫做结点。
链表可分为单链表、双链表、循环链表。
基本算法
- 创建链表
顺序表存储结构的创建就是数组的初始化,即声明一个类型和大小的数组并赋值。单链表是一种松散的存储结构,动态的,同时指针域,必须为指针域赋值。创建链表的过程就是动态生成链表的过程。从空表开始,依次建立各个元素结点,逐个插入链表。
头插法:采用插队的方式,始终让新建结点在第一的位置上。类似堆栈。
假设头指针后有数据,往里插入结点即可。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 LNode* pTempHead = *ppHead; 2 pTempHead->pNext = NULL; 3 for (int i= 0;i<num;i++) 4 { 5 LNode* item = new LNode();//生成新的结点 6 item->data = i; 7 //这是头插法,就是在链表顶端往下插入 8 item->pNext = pTempHead->pNext; 9 pTempHead->pNext = item;//插入到表头,新插入的结点在表头 10 11 }
数组数据显示是:4,3,2,1,0
尾插法:每次新的结点都插在终端结点的后面。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 LNode* pTempHead = *ppHead; 2 //建立一个带头结点的单链表 3 for (int i= 0;i<num;i++) 4 { 5 LNode* item = new LNode();//生成新的结点 6 item->data = i; 7 pTempHead->pNext = item;//将表尾终端结点的指针指向新的结点,此时pphead中添加了结点,item在最后端 8 pTempHead = item;//继续让pTempHead指向ppHead的终端。 9 10 } 11 pTempHead->pNext = NULL;//尾插法是在这设置为null
链表数据显示:0,1,2,3,4.
- 插入结点
核心算法就是如图中
现将p的后继结点保存,再赋值给s的后继结点,就成了s->Next = p->Next 。此时将s添加到链表中:p->Next = s; 单链表的表头和表尾的特殊情况,操作时相同的。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //插入元素 2 void SingleList::InsertElem(LNode** ppHead,int pos,int data) 3 { 4 pLinkList pTempHead = *ppHead;//指向需要操作的数据链 5 if (pTempHead == NULL || pTempHead->pNext == NULL) 6 { 7 return; 8 } 9 pLinkList item; 10 int i = 0; 11 //选找到制定位置的结点,有效的可以在表尾和表头插入结点 12 while(pTempHead && i< pos) 13 { 14 pTempHead = pTempHead->pNext; 15 i++; 16 } 17 //位置不合适。有可能到了表尾此处不能为pTempHead->pNext,当为表尾是next没有分配内存,导致溢出。 18 if (!pTempHead ||i>pos) 19 { 20 return; 21 } 22 //进行插入操作 23 item = new LNode(); 24 if (!item) 25 { 26 return; 27 } 28 item->data = data; 29 item->pNext = pTempHead->pNext; 30 pTempHead->pNext = item; 31 32 }
- 删除结点
删除结点就是将他的前级结点的指针绕过即可。实际上就一步:p->next = p->next-next;同时释放点指针。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //删除元素 2 void SingleList::RemoveElem(LNode** ppHead,int pos,int* getData) 3 { 4 pLinkList pTempHead = *ppHead;//指向需要操作的数据链 5 if (pTempHead == NULL || pTempHead->pNext == NULL) 6 { 7 return; 8 } 9 pLinkList item; 10 int i = 0; 11 //选找到制定位置的结点,此处是pTempHead->pNext,有效的能删除表尾,防止访问到表尾的next 12 while(pTempHead->pNext && i< pos) 13 { 14 pTempHead = pTempHead->pNext; 15 i++; 16 } 17 //位置不合适。有可能到了表尾,如果和插入时为ptempHead,此时item的item->next会报错 18 if (!pTempHead->pNext ||i>pos) 19 { 20 return; 21 } 22 item = pTempHead->pNext; 23 *getData = item->data; 24 //item->pNext = pTempHead->pNext; 25 pTempHead->pNext = item->pNext; 26 //释放资源 27 delete item; 28 }
在删除结点和插入结点时,注意循环跳出的条件,两者不一样。
代码实例
返回类型 | 函数名称 | 参数 | 功能 |
---|---|---|---|
void | InitNode | (LNode** ppHead) | 初始化结构体 |
void | CreateList | (LNode** ppHead,int num) | 创建链表 |
void | DestroyList | (LNode** ppHead) | 清空链表 |
void | InsertElem | (LNode** ppHead,int pos,int data) | 插入元素 |
void | RemoveElem | (LNode** ppHead,int pos,int* getData) | 移除元素 |
bool | IsEmptyList | (LNode* pHead) | 是否为空 |
int | GetElem | (LNode* pHead,int pos,int* data) | 获取制定位置元素 |
void | PrintList | (LNode* pHead) | 输出链表 |
void | ReverseList | (LNode* pHead,LNode ** outList) | 反转链表 |
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 void SingleList::InitNode(LNode** ppHead) 2 { 3 if (*ppHead) 4 { 5 DestroyList(ppHead); 6 } 7 LNode* p = new LNode(); 8 p->data = 0; 9 p->pNext = NULL; 10 *ppHead = p; 11 } 12 //创建链表 13 void SingleList::CreateList(LNode** ppHead,int num) 14 { 15 InitNode(ppHead); 16 LNode* pTempHead = *ppHead; 17 //建立一个带头结点的单链表 18 //pTempHead = new LNode(); 19 //pTempHead->pNext = NULL; 20 for (int i= 0;i<num;i++) 21 { 22 LNode* item = new LNode();//生成新的结点 23 item->data = i; 24 //这是头插法,就是在链表顶端往下插入 25 //item->pNext = pTempHead->pNext; 26 //pTempHead->pNext = item;//插入到表头,新插入的结点在表头 27 //这是头插法 28 //item->pNext = pTempHead;//将表尾终端节点的指针指向新的结点,此时item是多个点 29 //pTempHead = item;//将item赋值给item 30 pTempHead->pNext = item; 31 pTempHead = item; 32 33 } 34 35 pTempHead->pNext = NULL;//尾插法是在这设置为null 36 //*ppHead = pTempHead; 37 } 38 //销毁链表 39 void SingleList::DestroyList(LNode** ppHead) 40 { 41 pLinkList pTempHead = *ppHead; 42 //前期判断,已经为空,返回 43 if (pTempHead == NULL||pTempHead->pNext == NULL) 44 { 45 return; 46 } 47 pLinkList item,item1; 48 item = pTempHead->pNext; 49 while(item)//没到表尾 50 { 51 item1= item->pNext; 52 delete item; 53 item = item1; 54 } 55 pTempHead->pNext = NULL; 56 } 57 //插入元素 58 void SingleList::InsertElem(LNode** ppHead,int pos,int data) 59 { 60 pLinkList pTempHead = *ppHead;//指向需要操作的数据链 61 if (pTempHead == NULL || pTempHead->pNext == NULL) 62 { 63 return; 64 } 65 pLinkList item; 66 int i = 0; 67 //选找到制定位置的结点 68 while(pTempHead && i< pos) 69 { 70 pTempHead = pTempHead->pNext; 71 i++; 72 } 73 //位置不合适。有可能到了表尾此处不能为pTempHead->pNext,当为表尾是next没有分配内存,导致溢出。 74 if (!pTempHead ||i>pos) 75 { 76 return; 77 } 78 //进行插入操作 79 item = new LNode(); 80 if (!item) 81 { 82 return; 83 } 84 item->data = data; 85 item->pNext = pTempHead->pNext; 86 pTempHead->pNext = item; 87 88 } 89 //删除元素 90 void SingleList::RemoveElem(LNode** ppHead,int pos,int* getData) 91 { 92 pLinkList pTempHead = *ppHead;//指向需要操作的数据链 93 if (pTempHead == NULL || pTempHead->pNext == NULL) 94 { 95 return; 96 } 97 pLinkList item; 98 int i = 0; 99 //选找到制定位置的结点,此处是pTempHead->pNext,有效的能删除表尾,防止访问到表尾的next 100 while(pTempHead->pNext && i< pos) 101 { 102 pTempHead = pTempHead->pNext; 103 i++; 104 } 105 //位置不合适。有可能到了表尾,如果和插入时为ptempHead,此时item的item->next会报错 106 if (!pTempHead->pNext ||i>pos) 107 { 108 return; 109 } 110 item = pTempHead->pNext; 111 *getData = item->data; 112 //item->pNext = pTempHead->pNext; 113 pTempHead->pNext = item->pNext; 114 //释放资源 115 delete item; 116 } 117 //判断单链表是否为空 118 bool SingleList::IsEmptyList(LNode* pHead) 119 { 120 if (NULL == pHead||NULL == pHead->pNext) 121 { 122 return true; 123 } 124 else 125 return false; 126 } 127 //获取单链表位置为pos的元素,从头开始找,直到第i元素为止,时间复杂度为N。 128 int SingleList::GetElem(LNode* pHead,int pos,int* data) 129 { 130 int j = 1;//计数器 131 if (pHead == NULL||pos<1) 132 { 133 return -1; 134 } 135 pLinkList item;//声明一个结点 136 item = pHead->pNext;//指向头结点 137 while(item && j<pos) 138 { 139 item =pHead->pNext; 140 j++; 141 } 142 *data = item->data; 143 return 1; 144 } 145 146 #pragma endregion 147 148 #pragma region 链表扩展操作 149 //从尾到头打印链表 150 void SingleList::PrintList(LNode* pHead) 151 { 152 //此处是反转打印链表,可以利用堆栈 153 stack<LNode*> stackList ; 154 pLinkList item = pHead; 155 while(item != NULL) 156 { 157 //压入堆栈 158 stackList.push(item); 159 item = item->pNext; 160 } 161 while(!stackList.empty()) 162 { 163 //导出数据 164 item = stackList.top(); 165 cout<<item->data; 166 stackList.pop(); 167 } 168 } 169 170 void SingleList::PrintList1(LNode* pHead) 171 { 172 if (NULL == pHead || NULL == pHead->pNext) { 173 174 printf("LinkList is empty "); 175 176 return; 177 178 } 179 180 LNode *p = pHead->pNext; 181 182 printf("LinkList:"); 183 184 while (p) { 185 186 printf(" %d", p->data); 187 188 p = p->pNext; 189 190 } 191 192 printf(" "); 193 } 194 //反转链表 195 void SingleList::ReverseList(LNode* pHead,LNode ** outList) 196 { 197 if (pHead == NULL||pHead->pNext == NULL) 198 { 199 return; 200 } 201 pLinkList pTempHead = *outList; 202 pTempHead = new LNode(); 203 pLinkList pCurrent = pHead; 204 while(pCurrent) 205 { 206 LNode * item = pCurrent; 207 //采用尾插法,创建链表即可; 208 item->pNext = pTempHead; 209 pTempHead = item; 210 pCurrent = pCurrent->pNext; 211 } 212 213 }
面试题
查找单链表中的倒数第K个结点(k > 0)
思路:使用两个指针,先让前面的指针走到正向第k个结点,这样前后两个指针的距离差是k-1,之后前后两个指针一起向前走,前面的指针走到最后一个结点时,后面指针所指结点就是倒数第k个结点。
// 查找单链表中倒数第K个结点
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 LNode * RGetKthNode(LNode * pHead, unsigned int k) // 函数名前面的R代表反向 2 { 3 if(k == 0 || pHead == NULL) // 这里k的计数是从1开始的,若k为0或链表为空返回NULL 4 return NULL; 5 6 LNode * pAhead = pHead; 7 LNode * pBehind = pHead; 8 while(k > 1 && pAhead != NULL) // 前面的指针先走到正向第k个结点 9 { 10 pAhead = pAhead->m_pNext; 11 k--; 12 } 13 if(k > 1 || pAhead == NULL) // 结点个数小于k,返回NULL 14 return NULL; 15 while(pAhead->m_pNext != NULL) // 前后两个指针一起向前走,直到前面的指针指向最后一个结点 16 { 17 pBehind = pBehind->m_pNext; 18 pAhead = pAhead->m_pNext; 19 } 20 return pBehind; // 后面的指针所指结点就是倒数第k个结点 21 }
查找单链表的中间结点
思路:设置两个指针,只不过这里是,两个指针同时向前走,前面的指针每次走两步,后面的指针每次走一步,前面的指针走到最后一个结点时,后面的指针所指结点就是中间结点。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 这里写代码片// 获取单链表中间结点,若链表长度为n(n>0),则返回第n/2+1个结点 2 ListNode * GetMiddleNode(LNode * pHead) 3 { 4 if(pHead == NULL || pHead->m_pNext == NULL) // 链表为空或只有一个结点,返回头指针 5 return pHead; 6 7 L * pAhead = pHead; 8 L * pBehind = pHead; 9 while(pAhead->m_pNext != NULL) // 前面指针每次走两步,直到指向最后一个结点,后面指针每次走一步 10 { 11 pAhead = pAhead->m_pNext; 12 pBehind = pBehind->m_pNext; 13 //防止表尾,内存溢出.走了两步 14 if(pAhead->m_pNext != NULL) 15 pAhead = pAhead->m_pNext; 16 } 17 return pBehind; // 后面的指针所指结点即为中间结点 18 }
已知两个单链表pHead1 和pHead2 各自有序,把它们合并成一个链表依然有序
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 // 合并两个有序链表 2 ListNode * MergeSortedList(ListNode * pHead1, ListNode * pHead2) 3 { 4 //如果head1为空,返回head2 5 if(pHead1 == NULL) 6 return pHead2; 7 //如果head2为空,返回head1,这些处理防止后面访问链表中元素时,内存溢出 8 if(pHead2 == NULL) 9 return pHead1; 10 ListNode * pHeadMerged = NULL; 11 //比较大小,确定合并链表的表头指向谁。 12 if(pHead1->m_nKey < pHead2->m_nKey) 13 { 14 pHeadMerged = pHead1; 15 pHeadMerged->m_pNext = NULL; 16 pHead1 = pHead1->m_pNext; 17 } 18 else 19 { 20 pHeadMerged = pHead2; 21 pHeadMerged->m_pNext = NULL; 22 pHead2 = pHead2->m_pNext; 23 } 24 ListNode * pTemp = pHeadMerged; 25 //开始向合并链表中添加节点 26 while(pHead1 != NULL && pHead2 != NULL) 27 { 28 if(pHead1->m_nKey < pHead2->m_nKey) 29 { 30 //head1中元素小,通过尾插法,将小的结点先放在链表尾。 31 //将head1插入链表中 32 pTemp->m_pNext = pHead1; 33 //head1下移,继续循环 34 pHead1 = pHead1->m_pNext; 35 //让temp始终指向表尾,也是就next 36 pTemp = pTemp->m_pNext; 37 //表尾设置为null. 38 pTemp->m_pNext = NULL; 39 } 40 else 41 { 42 pTemp->m_pNext = pHead2; 43 pHead2 = pHead2->m_pNext; 44 pTemp = pTemp->m_pNext; 45 pTemp->m_pNext = NULL; 46 } 47 } 48 if(pHead1 != NULL) 49 pTemp->m_pNext = pHead1; 50 else if(pHead2 != NULL) 51 pTemp->m_pNext = pHead2; 52 return pHeadMerged; 53 }
版权声明:本文为博主原创文章,未经博主允许不得转载。