1. 单链表
(1)实现一个单链表
【注】这里不是要求创建一个单链表类,否则会涉及到许多的类成员函数的定义,而这只是一个小编程题而已,不要小题大做。这里仅仅是要求建立一个(特定的)单链表而已。
#include <stdafx.h>
#include <iostream>
using namespace std;
struct Node
{
int data;
Node *next;
Node(const int _data=0, Node *_next=NULL):data(_data), next(_next){}
};
int main()
{
int a[10] = {1,2,3,4,5,6,7,8,9,10};//笔试中的程序不必用scanf或cin进行输入,直接定义一个数组说明问题即可!
Node *head = new Node(a[0]);
Node *p = head; //p为遍历链表的指针
for(int i=1; i!=10; ++i)
{
Node *newNode = new Node(a[i]);
p->next = newNode;
p = p->next;
}
//p->next = NULL; //【注】如果不定义默认构造函数的话则千万不能落掉这一条语句!
for(p=head; p!=NULL; p=p->next) //既然是链表,就应该用指针或迭代器来对齐遍历
cout << p->data << " ";
cout << endl;
}
(2)单链表的删除
void deleteNode(Node *&head, const int item) //注意这里是对head的引用,从而可以返回新的链表首地址
{
Node *currPtr, *prevPtr;
for(prevPtr=NULL, currPtr=head; currPtr!=NULL && currPtr->data!=item; prevPtr=currPtr, currPtr=currPtr->next);
if(currPtr == head) //第一种特殊情况:被删除结点为表首结点
{
head = head->next;
delete currPtr;
}
else if(NULL == currPtr) //第二种特殊情况,未找到数据等于item的结点
{
cerr << "can not find " << item << endl;
exit(1);
}
else //一般情况
{
prevPtr->next = currPtr->next;
delete currPtr;
}
}
(3)单链表的插入
void insertNode(Node *&head, const int item)//注意这里是对head的引用,从而可以返回新的链表首地址
{
Node *currPtr, *prevPtr; //prevPtr是currPtr前一个结点的指针
Node *newNode = new Node(item);
for(prevPtr=NULL, currPtr=head; currPtr!=NULL && item>=currPtr->data; prevPtr = currPtr, currPtr=currPtr->next);
//【注】在for循环的终止条件中,currPtr!=NULL必须在currPtr->data<newNode->data前面,否则,当currPtr==NULL时,不会
//再有currPtr->data,于是运行出错。这是由于“&&”前后的语句执行的顺行问题造成的,如果前面的条件不符合,后面的条
//件语句则不再执行。事实上,如果将currPtr->data<newNode->data放在循环内作为if条件的话就不会有这种情况了,不过这
//样会出现冗余的循环。
if(NULL == prevPtr) //第一种特殊情况:新结点的值小于原链表头结点的值,新结点应插入表首(等价于NULL == prevPtr)
{
newNode->next = head;
head = newNode;
}
else if(NULL == currPtr) //第二种特殊情况:节点的值大于原链表最后一个结点的值,新结点应插入表尾
prevPtr->next = newNode;
//【注】这里如果改为currPtr = newNode则为错误的,因为prevPtr->next仍然为NULL,而currPtr = newNode仅仅是修改
//了currPtr这个指针的值而已,而prevPtr->next并未改变(因为二者并未通过指针或引用相联系)。
else//新结点应插入链表中间
{
newNode->next = currPtr;
prevPtr->next = newNode;
}
}
(4)单链表的打印
void printList(const Node *head)
{
const Node *currPtr; //由于currPtr不是const指针,故不用在声明时初始化。
//这里指针类型必须为const Node *型,否则由于const Node *不能转化为Node *而导致语句“currPtr = head”编译出错。
for(currPtr = head; currPtr!=NULL; currPtr=currPtr->next)
cout << currPtr->data << " ";
cout << endl;
}
(5)单链表的测长
int sizeList(const Node *head)
{
int n = 0;
const Node *currPtr; //由于currPtr不是const指针,故不用在声明时初始化。
for(currPtr=head; currPtr!=NULL; currPtr=currPtr->next)
++n;
return n;
}
(6)单链表的排序
① 选择排序法:
void selectSort(Node *head) //这里参数的类型是不是引用都一样,因为函数体并没有对结点的位置做变化,而改变的仅仅是内容
{
Node *p1, *p2, *smallPtr;
for(p1=head; p1->next!=NULL; p1=p1->next)
{
smallPtr = p1;
for(p2=p1->next; p2!=NULL; p2=p2->next)
if(p2->data < smallPtr->data)
smallPtr = p2;
swap(p1->data, smallPtr->data); //结点本身的位置并不交换,而只交换两个结点之间的数据,从而使问题得到了简化
}
}
② 交换排序法:
void exchangeSort(Node *head) //这里参数的类型是不是引用都一样,因为函数体并没有对结点的位置做变化,而改变的仅仅是内容
{
if(NULL == head)
return;//防止后面调用直接成员操作符时空结点的未定义错误
Node *p1, *p2;
for(p1=head; p1->next!=NULL; p1=p1->next)
{
for(p2=p1->next; p2!=NULL; p2=p2->next)
{
if(p2->data < p1->data)
swap(p1->data, p2->data);
}
}
}
③ 冒泡排序法:
void bubbleSort(Node *head)//这里参数的类型是不是引用都一样,因为函数体并没有对结点的位置做变化,而改变的仅仅是内容
{
Node *p1, *p2; //用于排序
Node *lastExchangePtr; //冒泡排序法的主线
//遍历链表,使得p1指向链表的最后一个结点,从而为下面冒泡法的循环做准备
for(p1=head; p1->next!=NULL; p1=p1->next);
for(; p1!=head; p1=lastExchangePtr)
{
lastExchangePtr = head; //防止内循环没有执行以致lastExchangePtr没有改变从而导致死循环
for(p2=head; p2!=p1; p2=p2->next)
{
if(p2->data > p2->next->data)
{
swap(p2->data, p2->next->data);
lastExchangePtr = p2;
}
}
}
}
【注】单链表排序不能用插入排序法进行排序,因为插入排序法涉及到了逆序遍历。外层循环为:for(int i=1; i<n; ++i),而内层循环为:for(int j=i-1; j>0 && a[j]>temp; --j),其中“--j”在单链表中没法表示。虽然冒泡排序法是外后内前,在外层也是逆序,但是其实现为for(i=n-1; i>0; i= lastExchangePos),它通过lastExchangePos的减小达到逆序的目的,从而不必通过指针依次逆序遍历指针,因此可以在单链表中实现。
(7)单链表的逆置
① 方法一(更好):
void reverseList(Node *&head)
{
if(NULL==head || NULL==head->next) //特殊情况:空链表或只有一个结点
return;
Node *prevPtr, *currPtr, *postPtr;//需要三个指针
for(prevPtr=NULL,currPtr=head,postPtr=head->next; currPtr->next!=NULL;
prevPtr=currPtr,currPtr=postPtr,postPtr=postPtr->next)//从第一个结点开始进行遍历,最后一个结点未连接
currPtr->next = prevPtr;
currPtr->next = prevPtr; //不要丢掉,因为最后一个结点没有和之前的结点像连接
head = currPtr;
}
② 方法二:
void reverseList(Node *&head)
{
if(NULL==head || NULL==head->next)
return;
Node *prevPtr, *currPtr, *postPtr;
prevPtr = head;
currPtr = head->next; //从第二个结点开始进行遍历,第一个结点未连接
while(currPtr != NULL)//以currPtr == NULL为判别终止条件
{
postPtr = currPtr->next; //仅当currPtr非NULL时才会有postPtr
currPtr->next = prevPtr; //每次逆置一次
prevPtr = currPtr; //指针后移,以便进行下一次循环
currPtr = postPtr;
}
head->next = NULL;
head = prevPtr;
}
(8)求单链表的中点
【要求】链表的结点数未知,怎样遍历一次就得出单链表的中间结点?
Node *searchMiddleNode(Node *head)
{
if(NULL == head) //而如果为空链表,NULL->next会导致运行错误,故需单独考虑
return NULL;
if(NULL == head->next) //而如果为单结点链表,NULL->next->next会导致运行错误,故需单独考虑
return head;
Node *p1, *p2;
for(p1=head, p2=head; p1->next!=NULL && p1->next->next!=NULL; p1=p1->next->next, p2=p2->next);
//一定要先检查p1->next!=NULL再检查p1->next->next!=NULL,顺序不能颠倒
return p2;
}
(9)合并两个已排序的链表
【要求】合并两个从小到大已排好序的链表,并形成一个新的具有同样顺序的链表
//【合并思想】首先的确定下新的表头结点;其次,currPtr指向最近加入到新链表中的结点,而它所指向的
//是min(head1->data, head2->data)所对应的结点(假如是head1),进而currPtr指向该结点(最近加入到
//链表中的结点);而head1指向该结点的下一个结点,以便进行下一次比较。知道head1或head2为NULL时,
//而未空的那一个子链表直接连到currPtr后即可。
Node *merge(Node *head1, Node *head2)
{
Node *head=NULL, *p;//新链表的表头head和遍历指针p
Node *p1, *p2, *smallPtr;
//p1和p2分别为待合并链表head1和head2的遍历指针,smallPtr为指向两个结点中值较小的结点的指针
for(p1=head1, p2=head2; p1!=NULL && p2!=NULL;)
{
if(p1->data < p2->data)
{
smallPtr = p1;
p1 = p1->next;
}
else
{
smallPtr = p2;
p2 = p2->next;
}
if(NULL == head)
{
head = smallPtr;
p = head;//p指向最近加入到新链表中的结点,即smallPtr
}
else
{
p->next = smallPtr;
p = p->next;//p指向最近加入到新链表中的结点,即smallPtr
}
}
//根据上面的循环终止条件,不是head1等于NULL就是head2等于NULL
if(NULL == p1)
p->next = p2;
else
p->next = p1;
return head;
}
(10)查找两个单链表中相同的元素
int calNumOfSame(int c[], Node *head1, Node *head2)//返回相同元素的个数
{
Node *p1, *p2;
int k = 0;
//先对两链表进行排序,然后再查找相同元素的个数
selectSort(head1);
selectSort(head2);
for(p1=head1, p2=head2; p1!=NULL && p2!=NULL;)
{
if(p1->data < p2->data)
p1 = p1->next;
else if(p1->data > p2->data)
p2 = p2->next;
else
{
c[k++] = p1->data;
p1 = p1->next;
p2 = p2->next;
}
}
return k;
}
//测试程序:
int main()
{
int a[] = {10,9,5,8,6,7,4,3,2,1};
int b[] = {10,8,6,2,4};
Node *head1, *head2, *p1, *p2;
head1 = new Node(a[0]);
p1 = head1;
head2 = new Node(b[0]);
p2 = head2;
for(int i=1; i!=10; ++i)
{
Node *newNode = new Node(a[i]);
p1->next = newNode;
p1 = p1->next;
}
for(int i=1; i!=5; ++i)
{
Node *newNode = new Node(b[i]);
p2->next = newNode;
p2 = p2->next;
}
int c[10];
int k = calNumOfSame(c, head1, head2);
for(int i=0; i<k; ++i)
cout << c[i] << " ";
cout << endl;
}