线性表基础介绍

     以下内容主要参考了严蔚敏版的数据结构教材,仅为加强学习,不做其他用途。
     线性结构的特点是在数据元素的非空有限集合内:

  • 存在唯一一个被称做“第一个”的数据元素
  • 存在唯一一个被称做“最后一个”的数据元素
  • 除第一个之外,集合中的每一个数据元素都只有一个前驱
  • 除最后一个之外,集合中的每一个数据元素都只有一个后继

     线性表是一种线性结构。线性表是 n n n个数据元素的有限序列,相邻数据元素之间存在着序偶关系。如果将线性表记为:

  • a 1 , a 2 , a 3 , . . . , a i − 1 , a i , a i + 1 , . . . , a n a_1,a_2,a_3,...,a_{i-1},a_i,a_{i+1},...,a_n a1,a2,a3,...,ai1,ai,ai+1,...,an

则表中 a i − 1 a_{i-1} ai1领先于 a i a_{i} ai a i a_{i} ai领先于 a i + 1 a_{i+1} ai+1,称 a i − 1 a_{i-1} ai1 a i a_{i} ai的直接前驱, a i + 1 a_{i+1} ai+1 a i a_{i} ai的直接后继。当 i = 1 , 2 , . . . , n − 1 i=1,2,...,n-1 i=1,2,...,n1时, a i a_i ai有且仅有一个直接后继,当 i = 2 , 3 , . . . , n i=2,3,...,n i=2,3,...,n时, a i a_i ai有且仅有一个直接前驱。线性表中元素的个数 n ( n ≥ 0 ) n(n\ge0) n(n0)定义为线性表的长度, n = 0 n=0 n=0时成为空表。在非空表中每个数据元素都有一个确定的位置,如 a 1 a_1 a1是第一个数据元素, a n a_n an是最后一个数据元素, a i a_i ai是第 i i i个数据元素,称 i i i为数据元素 a i a_i ai在线性表中的位序。数据元素可以是原子类型数据也可以是结构类型数据,例如数据元素可以是一个英文字母,也可以是一个包含许多元素的结构体变量。
     线性表在计算机中的实现有两种方式,一种是顺序表示,一种是链式表示。我们首先讲顺序表示。顺序表示利用一组地址连续的内存空间依次存储线性表的数据元素,这样就利用了内存单元的物理位置关系来表示线性表每一个元素之间的序偶关系和位置关系。如图1所示,假设每个数据元素占用 l l l个字节的内存单元。

 
图1.

     只要确定了第一个数据元素 a 1 a_1 a1的内存起始地址 m m m,就可以很快确定线性表中其它数据元素 a 1 a_1 a1的起始地址 m + ( i − 1 ) l m+(i-1)l m+(i1)l,因此获取线性表中每一个数据元素的时间为常数,线性表的这种表示叫做顺序表,这是一种随机存取的存储结构。下面直接上代码吧。下面只是简单的实现了插入删除操作以及合并两个数据元素已经按非递减顺序排列的两个线性表为一个数据元素按非递减顺序排列的线性表。因为本身顺序表就比较简单,我们这里就不多说了。

/*File:Header.h*/
#pragma once
#include <iostream>
using namespace std;

class SqList
{
private:
	int length;
	int listsize;
	int* elem;
public:
	SqList()
	{
		length=0;
		listsize=100;
		elem=new int[listsize];
		if (elem==nullptr)
		{
			cout << "Memory allocation is failed."<<endl;
		}
	}
	~SqList()
	{
		delete [] elem;
	}
	int SqListInsert(int position, int insertElement);
	int SqListDelete(int position,int& deletedValue);
	int SqListLength();
	int getElement(int position);
	void printList();
};

int SqList::SqListInsert(int position, int insertElement)
{
	int* insertIndex = nullptr;
	if ((position < 0) || (position > length))
		return 0;
	if (length == listsize)
	{
		int* newbase = new int[length + 100];
		if (newbase == nullptr)
		{
			cout << "Memory reallocation is failed." << endl;
		}
		for (int i = 0; i++; i < length)
		{
			newbase[i] = elem[i];
		}
		delete[] elem;
		listsize = listsize + 100;
		elem = newbase;
	}
	insertIndex = elem + position;
	for (int* temp = elem + length - 1; temp >= insertIndex; temp--)
	{
		*(temp + 1) = *temp;
	}
	*insertIndex = insertElement;
	length++;
	return 1;
}

int SqList::SqListDelete(int position, int& deletedValue)
{
	if ((position < 0) || (position > (length-1)))
		return 0;
	deletedValue = elem[position];
	int *deletedIndex = elem + position;
	for (int* temp = deletedIndex+ 1; temp <= elem+length-1; temp++)
	{
		*(temp - 1) = *temp;
	}
	length--;
	return 1;
}
int SqList::SqListLength()
{
	return length;
}

int SqList::getElement(int position)
{
	if ((position >= 0) && (position < length))
	{
		return elem[position];
	}
	else
	{
		cout << "index error" << endl;
	}

}

void SqList::printList()
{
	for (int i = 0; i < length; i++)
	{
		cout << elem[i] << endl;
	}
	return ;
}

void mergeSqList(SqList A, SqList B, SqList &C)
{
	int indexA = 0;
	int indexB = 0;
	int indexC = 0;
	while(indexA<A.SqListLength()&& indexB < B.SqListLength())
	{ 
		if (A.getElement(indexA) <= B.getElement(indexB))
		{
			C.SqListInsert(indexC, A.getElement(indexA));
			indexA++;
			indexC++;
		}
		else
		{
			C.SqListInsert(indexC, B.getElement(indexB));
			indexB++;
			indexC++;
		}
	}

	while (indexA < A.SqListLength())
	{
	    C.SqListInsert(indexC, A.getElement(indexA));
		indexA++;
		indexC++;
	}

	while (indexB < B.SqListLength())
	{
		C.SqListInsert(indexC, B.getElement(indexB));
		indexB++;
		indexC++;
	}
	return;
}
#include"Header.h"

int main()
{
	SqList A;
	SqList B;
	SqList C;
	for (int i = 0; i < 5; i++)
	{
		A.SqListInsert(i,i+1);
		B.SqListInsert(i, i + 2);
	}
	cout << "The SQ list A is:" << endl;
	A.printList();
	cout << "The SQ list B is:" << endl;
	B.printList();
	mergeSqList(A, B, C);
	cout << "The SQ list C is:" << endl;
	C.printList();
	return 1;
}
 
图2.

     这里有一个知识点,我也算是复习一下, C + + C++ C++在传递函数参数的时候实际上是将传递过来的参数复制了一份,如果函数参数为类对象,会调用该类的拷贝构造函数来进行参数的复制就是重新构造了临时的在函数范围内有效的类对象,如果没有定义拷贝构造函数的话,编译器会自动合成一个简单版本的拷贝构造函数,该拷贝构造函数只是简单的将实际参数类的数据成员变量复制给形式参数的成员变量。如果成员变量是指针的话,那就相当于是由两个类对象的成员变量指向了同一个地址块,这样是不太好的。
     为了验证函数参数为类对象时,会调用拷贝构造函数来创建临时的在函数范围内有效的类对象,我新建了一个拷贝构造函数。如下所示。这里我在拷贝构造函数中重新分配了内存单元防止被复制的对象和复制构成的对象的存储线性表中元素的指针 e l e m elem elem指向同一块内存区域,如果拷贝构造函数修改了这块内存区域的内容,也就是修改了被复制对象相应的内容。图3是加入拷贝构造函数之后的测试结果。

	SqList(const SqList & orig)
	{
		length = orig.length;
		listsize = orig.listsize;
		elem = new int[listsize];
		if (elem == nullptr)
		{
			cout << "Memory allocation is failed." << endl;
		}
		for (int i = 0; i < length; i++)
		{
			elem[i] = orig.elem[i];
		}
		cout << "This is the copy constructor." << endl;
	}
 
图3.

     图3是加入拷贝构造函数之后的测试结果。还有一点需要注意的是拷贝构造函数里面的以下三句代码,可能很多人和我一样,也有点不理解,为啥可以类对象直接访问 p r i v a t e private private类型对象。其实我这个现在没有查找太多资料去深入了解,只是根据这位哥们的经验大概理解了以下,留待以后深入了解。大意就是此时这三段代码对私有成员的访问依然还是在类体中,也就是说 p r i v a t e private private属性是针对类的而不是类对象。但是如果在 m a i n main main函数中这么调用的话肯定会报错,因为此时对私有成员的访问不在类体中。

		length = orig.length;
		listsize = orig.listsize;
		elem[i] = orig.elem[i];

     接下来我们讲线性表在计算机中的链式表示。线性表的顺序表示的特点是逻辑关系上相邻的两个元素在物理内存上也相邻,因此可以随意存储任一元素,但是它需要一整块内存单元来存储并且插入和删除需要移动大量元素。线性表的链式表示不要求一整块的内存空间来存储线性表的所有元素,这样自然就不要求逻辑关系上相邻的两个元素在物理内存上也相邻,因此避免了顺序表示的缺点但是同时也失去了顺序表示的随机存取的优点。线性表的链式表示的每一个数据元素可以分布在内存空间的任意地址。
     图4是一个简单的线性表的链式表示的结构示意图,左边的表示一个有数据元素的线性表,右边的表示一个没有元素的空表。线性表的链式表示的每一个数据节点除了存储节点信息外还存储了指向在线性表的逻辑关系中位于该节点的下一个节点的指针(图中蓝色箭头),线性表的链式表示用该信息来表示线性表中的元素的前后逻辑关系。图4中的线性表的链式表示是包含“头节点”的。“头节点”的不包含任何节点信息,只是其指针指向线性表的第一个数据元素。指针 L L L指向头结点,头结点不是必须的,可没有头结点,这样指针 L L L就指向线性表的第一个节点。

 
图4.

     这里线性表的链式表示的具体操作和上面的线性表示的一样,也只有掺入、删除、合并等操作,算法思想基本和线性表示的一样,也都比较简单,因此下面直接上代码。测试结果如图5所示。这里需要注意的是,我在这里对 L i n k L i s t LinkList LinkList类定义了拷贝构造函数,并且析构函数中有内容,因为只有这样 v o i d m e r g e L i n k L i s t ; void\quad mergeLinkList; voidmergeLinkList;函数的前两个参数才能为 L i n k L i s t A LinkList \quad A LinkListA这样的形式,如果没有定义拷贝构造函数,但是析构函数中的内容相同,则此时 v o i d m e r g e L i n k L i s t ; void\quad mergeLinkList; voidmergeLinkList;函数的前两个参数必须为 L i n k L i s t & A LinkList\quad \& A LinkList&A这样的引用形式。这里有下面几点需要注意:

  • 如果 v o i d m e r g e L i n k L i s t ; void\quad mergeLinkList; voidmergeLinkList;函数的前两个参数为 L i n k L i s t A LinkList \quad A LinkListA这样的形式,但是又没有定义拷贝构造函数,这样会导致调用函数 v o i d m e r g e L i n k L i s t ; void\quad mergeLinkList; voidmergeLinkList;时复制传递的类参数 A A A B B B。这里的复制是由编译器合成的拷贝构造函数完成的,这里的复制只是简单的成员复制,因此会导致类 A A A B B B和类 A A A B B B被复制的类的数据成员节点指向同一内存空间,调用函数 v o i d m e r g e L i n k L i s t ; void\quad mergeLinkList; voidmergeLinkList;结束后,在析构类 A A A B B B被复制的类时会把类 A A A B B B的数据元素的空间也给删了,导致堆出现一些错误( n e w new new出来的对象一般放在堆),如图6所示,这里的错误只会在 D E B U G DEBUG DEBUG模式下出现,在 R E L E A S E RELEASE RELEASE模式下不会出现,感兴趣的可以试一下,这里是我个人的一点猜测和理解,不一定准确。
  • 如果 v o i d m e r g e L i n k L i s t ; void\quad mergeLinkList; voidmergeLinkList;函数的前两个参数为 L i n k L i s t & A LinkList \quad \&A LinkList&A这样的引用形式,但是又没有定义拷贝构造函数,这样不会出现参数复制,而是直接使用的类对象 A A A B B B。因此也不会出现以上的几个类对象的数据成员指向同一个内存空间的情况。这里在 D E B U G DEBUG DEBUG模式测试就可以 P A S S PASS PASS
  • 如果 v o i d m e r g e L i n k L i s t ; void\quad mergeLinkList; voidmergeLinkList;函数的前两个参数为 L i n k L i s t A LinkList \quad A LinkListA这样的形式,但是定义了拷贝构造函数,这样会导致调用函数 v o i d m e r g e L i n k L i s t ; void\quad mergeLinkList; voidmergeLinkList;时复制传递的类参数 A A A B B B。这里的复制是由定义的拷贝构造函数完成的,它为链表中的每一个节点又重新分配了空间,因此不会导致类 A A A B B B和类 A A A B B B被复制的类的数据成员节点指向同一内存空间,这里在 D E B U G DEBUG DEBUG模式测试就可以 P A S S PASS PASS
  • L i n k L i s t LinkList LinkList类的成员函数 g e t E l e m e n t getElement getElement被定义为 c o n s t const const类型,这样是为了在我自己定义的拷贝构造函数中 c o n s t const const类参数 c o p y copy copy可以调用成员函数 g e t E l e m e n t getElement getElement。因为常对象只能调用常成员函数。
#pragma once
#include <iostream>
using namespace std;
class ListNode
{
private:
	int data;
	ListNode* next;
public:
	ListNode()
	{
		data = 0;
		next = nullptr;
	}
	ListNode(int d_value, ListNode* p_value)
	{
		data = d_value;
		next = p_value;
	}
	ListNode* getNext()
	{
		return next;
	}
	int getData()
	{
		return data;
	}
	void setNext(ListNode* value)
	{
		next = value;
		return;
	}
	void setData(int value)
	{
		data = value;
		return;
	}
};
class LinkList
{
private:
	int length;
	ListNode* head;
public:
	LinkList()
	{
		length=0;
		head = new ListNode();
	}
	LinkList(const LinkList& copy)
	{
		cout << "This copy constructor." << endl;
		int nodeIndex = 1;
		length = 0;
		head = new ListNode();
		while (nodeIndex <= copy.length)
		{
			LinkListInsert(nodeIndex, copy.getElement(nodeIndex));
			nodeIndex++;
		}
	}
	~LinkList()
	{
		ListNode* current=head;
		ListNode* next=head->getNext();
		while (next!=nullptr)
		{
			delete current;
			current = next;
			next = next->getNext();
		}
		delete current;
	}
	int LinkListInsert(int position, int insertElement);
	int LinkListDelete(int position,int& deletedValue);
	int LinkListLength();
	int getElement(int position) const;
	void printList();
};

int LinkList::LinkListInsert(int insertPosition, int insertElement)
{
	ListNode* currentNode = head;
	ListNode* insertNode = new ListNode(insertElement,nullptr);
	int currentPosition = 0;
	if ((insertPosition < 1) || (insertPosition > (length+1)))
	{ 
		cout << "InsertPosition error,insertPosition=" << insertPosition << ".length=" << length << endl;
		return 0;
	}
	while (currentPosition != (insertPosition-1)) 
	{
		currentPosition++;
		currentNode = currentNode->getNext();
	}
	insertNode->setNext(currentNode->getNext());
	currentNode->setNext(insertNode);
	length++;
	return 1;
}

int LinkList::LinkListDelete(int deletePosition, int& deletedValue)
{
	ListNode* currentNode = head;
	ListNode* tempNode = nullptr;
	int currentPosition = 0;
	if ((deletePosition < 1) || (deletePosition > length))
	{
		cout << "DeletePosition error,deletePosition=" << deletePosition << ".length=" << length << endl;
		return 0;
	}
	while (currentPosition != (deletePosition - 1))
	{
		currentPosition++;
		currentNode = currentNode->getNext();
	}
	deletedValue = currentNode->getNext()->getData();
	tempNode = currentNode->getNext();
	currentNode->setNext(tempNode->getNext());
	delete tempNode;
	length--;
	return 1;
}
int LinkList::LinkListLength()
{
	return length;
}

int LinkList::getElement(int position) const
{
	ListNode* currentNode = head->getNext();
	int currentPosition = 1;
	if ((position <1) || (position > length))
	{
		cout << "Position index error. Link list length is" << length<<endl;
		return -1;
	}
	while ((currentPosition!= position)&&(currentNode!=nullptr))
	{
		currentPosition++;
		currentNode = currentNode->getNext();
	}
	return currentNode->getData();
}

void LinkList::printList()
{
	if (length == 0)
	{
		cout << "The list is null." << endl;
		return;
	}
	ListNode* currentNode = head->getNext();
	while(currentNode!=nullptr)
	{
		cout << currentNode->getData() << endl;
		currentNode = currentNode->getNext();
	}
	return;
}

void mergeLinkList(LinkList A, LinkList B, LinkList &C)
{
	int indexA = 1;
	int indexB = 1;
	int indexC = 1;
	while(indexA<=A.LinkListLength()&& indexB <= B.LinkListLength())
	{ 
		if (A.getElement(indexA) <= B.getElement(indexB))
		{
			C.LinkListInsert(indexC, A.getElement(indexA));
			indexA++;
			indexC++;
		}
		else
		{
			C.LinkListInsert(indexC, B.getElement(indexB));
			indexB++;
			indexC++;
		}
	}

	while (indexA <= A.LinkListLength())
	{
	    C.LinkListInsert(indexC, A.getElement(indexA));
		indexA++;
		indexC++;
	}

	while (indexB <= B.LinkListLength())
	{
		C.LinkListInsert(indexC, B.getElement(indexB));
		indexB++;
		indexC++;
	}
	return;
}

#include"Header.h"

int main()
{
	LinkList A;
	LinkList B;
	LinkList C;
	for (int i = 1; i < 6; i++)
	{
		A.LinkListInsert(i, i + 1);
		B.LinkListInsert(i, i + 2);
	}
	cout << "The link list A is:" << endl;
	A.printList();
	cout << "The link list B is:" << endl;
	B.printList();
	mergeLinkList(A, B, C);
	cout << "The link list C is:" << endl;
	C.printList();
	return 1;
}
 
图5.
 
图6.

     我们在前面提到了,线性表的顺序表示用到了一块连续的内存空间来存储各个节点元素而线性表的链式表示则没有这个需求,可以是分散在内存各处的小块内存空间。线性表的顺序表示和链式表示各有各的优点。现在介绍一种结合线性表的顺序表示和链式表示来表示线性表的方法,叫做静态链表。话不多说,先看图7。图7是一个静态链表和其对应的线性表的链式表示的例子。从这里我们可看出线性表的静态链表表示也使用了一块连续的内存空间(也可以说是一维数组)来存储各个节点元素,但是不同点是在静态链表的每一个节点中除了存储节点本身的数据(图中 d a t a data data域)之外还存储了节点之间的逻辑关系的数据(图中 n e x t next next域),也就是当前节点的在线性表中的下一个节点在数组中的索引,这就类似于线性表的链式表示中的指针。静态链表在进行插入人删除时不需要移动数据元素,只需要更改节点的 n e x t next next域即可,这和线性表的链式表示一样。

 
图7.

     以下是具体的代码,图8是测试结果图。这里我们额外的定义了一个容器变量 n o _ u s e _ s p a c e no\_use\_space no_use_space来存储数组中没有使用的元素空间。当删除一个数组元素时我们会把该元素所占据的数组所索引存入容器变量 n o _ u s e _ s p a c e no\_use\_space no_use_space中,当插入一个节点时,我们会从容器变量 n o _ u s e _ s p a c e no\_use\_space no_use_space中取出并删除一个数组索引。以免空间浪费。这里数组空间索引为 0 0 0的第一个元素表示头结点,它的 n e x t next next指向线性表中的第一个节点。

#pragma once
#include <iostream>
#include <vector>
using namespace std;
class ListNode
{
private:
	int data;
	int next;
public:
	ListNode()
	{
		data = 0;
		next = -1;
	}
	ListNode(int d_value, int p_value)
	{
		data = d_value;
		next = p_value;
	}
	int getNext()
	{
		return next;
	}
	int getData()
	{
		return data;
	}
	void setNext(int value)
	{
		next = value;
		return;
	}
	void setData(int value)
	{
		data = value;
		return;
	}
};
class St_LinkList
{
private:
	int length;
	int listsize;
	int head;
	vector<int> no_use_space;
	ListNode* elem;
public:
	St_LinkList()
	{
		length = 0;
		head = 0;
		listsize = 10;
		elem = new ListNode[listsize];
		if (elem == nullptr)
		{
			cout << "Memory allocation is failed." << endl;
		}
		for (int i=1;i<listsize;i++)
		{
			no_use_space.push_back(i);
		}
	}

	~St_LinkList()
	{
		delete[] elem;
	}
	int St_LinkListInsert(int position, int insertElement);
	int St_LinkListDelete(int position, int& deletedValue);
	int St_LinkListLength();
	int St_LinkListMalloc();
	void St_LinkListFree(int k);
	int getElement(int position);
	void printList();
	void printArray();
};


void St_LinkList::St_LinkListFree(int k)
{
	no_use_space.push_back(k);
	return ;
}

int St_LinkList::St_LinkListMalloc()
{
	int temp = -1;
	if (!no_use_space.empty())
	{
		temp = no_use_space.back();
		no_use_space.pop_back();
	}
	else
	{
		cout << "No extra space is available!!!!!!!!!!!!!!!!!!!" << endl;
	}
	return temp;
}

int St_LinkList::St_LinkListInsert(int insertPosition, int insertElement)
{
	if ((insertPosition < 1) || (insertPosition > (length + 1)))
	{
		cout << "InsertPosition error,insertPosition=" << insertPosition << ".length=" << length << endl;
		return 0;
	}
	if (length == (listsize-1))
	{
		ListNode* newbase = new ListNode[listsize + 10];
		if (newbase == nullptr)
		{
			cout << "Memory reallocation is failed." << endl;
		}
		for (int i = 0; i < listsize;i++)
		{
			newbase[i] = elem[i];
		}
		delete[] elem;
		listsize = listsize + 10;
		elem = newbase;
		for (int i = listsize-10; i < listsize; i++)
		{
			no_use_space.push_back(i);
		}
	}
	int currentNode = head;
	int insertNode = St_LinkListMalloc();
	int currentPosition = 0;

	while (currentPosition != (insertPosition-1)) 
	{
		currentPosition++;
		currentNode = elem[currentNode].getNext();
	}
	elem[insertNode].setNext(elem[currentNode].getNext());
	elem[insertNode].setData(insertElement);
	elem[currentNode].setNext(insertNode);
	length++;
	return 1;
}

int St_LinkList::St_LinkListDelete(int deletePosition, int& deletedValue)
{
	int currentNode = head;
	int tempNode = -1;
	int currentPosition = 0;
	if ((deletePosition < 1) || (deletePosition > length))
	{
		cout << "DeletePosition error,deletePosition=" << deletePosition << ".length=" << length << endl;
		return 0;
	}
	while (currentPosition != (deletePosition - 1))
	{
		currentPosition++;
		currentNode = elem[currentNode].getNext();
	}
	deletedValue = elem[elem[currentNode].getNext()].getData();
	tempNode = elem[currentNode].getNext();
	elem[currentNode].setNext(elem[tempNode].getNext());
	St_LinkListFree(tempNode);
	length--;
	return 1;
}
int St_LinkList::St_LinkListLength()
{
	return length;
}

int St_LinkList::getElement(int position) 
{
	int currentNode = elem[head].getNext();
	int currentPosition = 1;
	if ((position <1) || (position > length))
	{
		cout << "Position index error. Link list length is" << length<<endl;
		return -1;
	}
	while ((currentPosition!= position)&&(currentNode!=-1))
	{
		currentPosition++;
		currentNode = elem[currentNode].getNext();
	}
	return elem[currentNode].getData();
}

void St_LinkList::printList()
{
	if (length == 0)
	{
		cout << "The list is null." << endl;
		return;
	}
	int currentNode = elem[head].getNext();
	while(currentNode!=-1)
	{
		cout << elem[currentNode].getData() << endl;
		currentNode = elem[currentNode].getNext();
	}
	return;
}

void St_LinkList::printArray()
{
	for (int i = 0; i < listsize; i++)
	{
		cout<<"index="<<i<<".data="<< elem[i].getData()<< ".next=" <<elem[i].getNext()<<endl;
	}
	return;
}

void mergeLinkList(St_LinkList &A, St_LinkList &B, St_LinkList&C)
{
	int indexA = 1;
	int indexB = 1;
	int indexC = 1;
	while(indexA<=A.St_LinkListLength()&& indexB <= B.St_LinkListLength())
	{ 
		if (A.getElement(indexA) <= B.getElement(indexB))
		{
			C.St_LinkListInsert(indexC, A.getElement(indexA));
			indexA++;
			indexC++;
		}
		else
		{
			C.St_LinkListInsert(indexC, B.getElement(indexB));
			indexB++;
			indexC++;
		}
	}

	while (indexA <= A.St_LinkListLength())
	{
	    C.St_LinkListInsert(indexC, A.getElement(indexA));
		indexA++;
		indexC++;
	}

	while (indexB <= B.St_LinkListLength())
	{
		C.St_LinkListInsert(indexC, B.getElement(indexB));
		indexB++;
		indexC++;
	}
	return;
}

#include"Header.h"

int main()
{
	St_LinkList A;
	St_LinkList B;
	St_LinkList C;
	for (int i = 1; i < 6; i++)
	{
		A.St_LinkListInsert(i, i + 1);
		B.St_LinkListInsert(i, i + 2);
	}
	cout << "The link list A is:" << endl;
	A.printList();
	cout << "The link list B is:" << endl;
	B.printList();
	mergeLinkList(A, B, C);
	cout << "The link list C is:" << endl;
	C.printList();
	cout << "The array in link list C is:" << endl;
	C.printArray();
	return 1;
}
 
图8.

     接下来还有循环链表、双向链表和循环双向链表,如图9所示。这些特别的链表和单链表的操作基本一样,但是在一些特别的情况下还是会有它们的特殊用途,且比单链表要方便许多。

  • 循环链表与单链表的区别是表中最后一个节点的指针域不为空而是指向了头结点,这样就造成整个链表形成一个环。由此从表中任意一个节点出发都可以到达表中的任意节点。
  • 双向链表的每一个节点中除了有指向其直接后继的指针域外还有指向其直接前驱的指针域,但是双向链表的头结点指向其直接前驱的指针域和双向链表中最后一个节点的指向其直接后继的指针域还是空指针。
  • 循环双向链表和双向链表的区别是循环双向链表的头结点指向其直接前驱的指针域指向了最后一个节点。循环双向链表中最后一个节点的指向其直接后继的指针域指向头节点。这样循环双向链表中就有两个环:一个顺着 n e x t next next指针域,一个顺着 p r i o r prior prior指针域。
 
图9.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
### 回答1: 《算法设计与分析基础》PDF是一本关于算法设计和分析的入门教材,它涵盖了算法的常用概念、基本思想和实际应用。本书第一章简要介绍了算法分析的基本方法和概念,包括时间复杂度和空间复杂度,为后续的章节打下了基础。 第二章通过实际例子引导读者如何设计和分析分治算法。该章节详细讲解了分治算法的基本概念和关键步骤,且对每个步骤都有具体的解释和示例,使读者能够更加深入地理解和应用分治算法。 第三章讨论了最基本的排序问题,包括冒泡排序、选择排序和插入排序等,每个排序算法都有详细的示例和伪代码。此,该章还介绍并分析了几种高级排序算法,如快速排序和归并排序。 第四章涉及贪心算法。该章节详细讨论了贪心策略的基本要素,以及许多常见的贪心算法,如背包问题、最小生成树和Huffman树等。 第五章介绍了动态规划算法。该章节分析了动态规划算法的基本思路和步骤,并且通过数个具体的例子说明了这种算法的应用。 除此之,该书还讲解了回溯法、分支限界法等一些经典的算法方法,并对一些重要的算法问题如最短路径问题、最小费用流问题进行了详细解释,使读者能够更加深入地理解和应用这些算法。 总而言之,《算法设计与分析基础》PDF为算法学习者提供了扎实的基础知识,涵盖了算法设计和分析的基本概念、思想和实际应用,同时还提供了众多的例题和习题,方便读者巩固和深入理解所学知识。 ### 回答2: 《算法设计与分析基础》是一本关于算法的入门教材,内容包括算法基础知识、数据结构、排序算法、图论算法等。此书旨在帮助学生从算法的角度分析问题,掌握算法设计和分析的基本方法。 本书首先介绍了算法的基本概念,例如时间复杂度、空间复杂度、渐近符号等。然后,本书详细讲解了几种基本的数据结构,例如线性表、树、图等,同时阐述了它们的实现方式和应用场景。此,本书还介绍了几种常用的排序算法,这些算法在实际应用非常重要。最后,本书讲解了图论算法,包括最短路径算法、最小生成树算法等。 该书目录清晰、内容详尽,每一章都有课后习题,可以帮助读者巩固所学的知识。此,本书还提供了一些优秀的实例来帮助读者理解算法。对于初学者而言,本书提供了一个详细而且易于理解的学习路径,能够从基础概念开始,逐步加深对算法的理解。 《算法设计与分析基础》是一本应用广泛的经典教材,适用于计算机科学、数学、物理等专业的学生,以及对算法设计和分析有兴趣的人士。同时,本书的PDF版本也非常便于学习和阅读,比较适合使用电子设备进行学习。 ### 回答3: 《算法设计与分析基础》是一本讲解算法设计和分析的重要参考书,可以用来帮助学生和研究人员理解和掌握算法深度思考的关键概念。这本书介绍了算法的基本思想和概念,并提供了许多实用的算法实现,帮助读者在问题求解时更加高效和准确。 本书的主要内容包括:算法的基本概念,递归算法,排序和查找算法,贪心算法,动态规划算法,图论算法,字符串算法以及数论算法等等。在这些章节,书籍详细解释了该算法的原理、算法实现以及在何种情况下使用该算法的具体实例。 此,这本书还着重讲解了算法的分析技巧,对于一个算法的时间复杂度、空间复杂度、稳定性等方面进行了详细解释,这有助于读者更好地评估和比较不同算法的优劣,以便在实际应用选择最优算法。 总的来说,《算法设计与分析基础》是一本非常优秀的书,它对算法设计和分析的基础知识进行了深入浅出的讲解,既简洁明了又不失深度,并且提供了大量实例和练习题,非常适合计算机专业学生、算法工程师以及数据研究人员进行学习和参考。如果你对算法设计和分析感兴趣,那么这本书一定不容错过。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qqssss121dfd

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值