(三)算法与数据结构 | 单链表


简介

线性表是具有相同特性的元素的一个有限序列。线性表的存储结构由顺序存储结构和链式存储结构,前者称为顺序表(有时也成为数组),后者称为链表。顺序表和链表的比较:

顺序表链表
随机访问特性不支持随机访问
占用连续的存储空间动态分配存储空间
每个结点只存储值结点除了存储值外,还需要存储下一结点的索引

(1)查找:对于按值查找,序列无序时,二者的时间复杂度均为 O ( n ) {\rm O(n)} O(n);序列有序时,顺序表的时间复杂度可以优化为 O ( l o g 2 n ) {\rm O(log_2n)} O(log2n)。对于按序号查找,顺序表的平均时间复杂度为 O ( 1 ) {\rm O(1)} O(1),链表的平均时间复杂度为 O ( n ) {\rm O(n)} O(n)。——排序算法总结
(2)插入、删除:顺序表的插入和删除平均需要移动半个表的元素,而链表的插入和删除只需修改相关结点的指针即可。
本文介绍链表当中最基本的单链表,其形式如下:
在这里插入图片描述

图1:单链表

:除特殊说明,本文单链表均为不带头结点的单链表。


0. 结构体定义

struct ListNode {
	int val;
	ListNode* next;
	ListNode(int x) : val(x), next(NULL) {}
};

使用方法:ListNode* T = new ListNode(3)语句即可声明一个值( v a l {\rm val} val)为3的单链表且T->next = NULL。


1. 建立链表

单链表的建立分为头插法和尾插法两种。头插法从一个空表开始,将读取到的数据放到新结点中,并将新结点插入到当前链表的表头,如图:
在这里插入图片描述

图2:头插法

每次插入结点时,在首结点前面依次插入元素。下面是单链表头插法的 C {\rm C} C++代码:

void createLinkList(ListNode* &head) {
	int ch;
	int flag = true;
	ListNode* temp;
	while (cin >> ch)
	{
		if (flag) {
			head = new ListNode(ch);
			head->next = NULL;
			flag = false;
		}
		else
		{
			if (ch != -1) {
				temp = new ListNode(ch);
				temp->next = head;
				head = temp;
			}
			else
			{
				break;
			}
		}
	}
}

头插法建立单链表时,初始结点需要单独处理,代码中定义了一个 f l a g {\rm flag} flag用于标识是否为第一个结点。 t e m p {\rm temp} temp待插入的新结点, h e a d {\rm head} head始终指向单链表的首结点。输入- 1 {\rm 1} 1时,表示单链表创建完成。同时,头插法建立的单链表与输入元素成逆序关系。如输入为 1   2   3 {\rm 1\ 2\ 3} 1 2 3 - 1 {\rm 1} 1,则建立的单链表为 3 → 2 → 1 → N U L L {\rm 3→2→1→NULL} 321NULL

尾插法建立单链表每个将新结点插入到当前链表的表尾上。如图:

在这里插入图片描述

图3:尾插法

每次插入结点时,在尾结点后面依次插入元素。下面是单链表尾插法的 C {\rm C} C++代码:

void createLinkList(ListNode* &head) {
	int ch;
	int flag = true;
	ListNode* temp = NULL;
	while (cin >> ch)
	{
		if (flag) {
			head = new ListNode(ch);
			temp = head;
			flag = false;
		}
		else
		{
			if (ch != -1)
			{
				ListNode* listNode = new ListNode(ch);
				temp->next = listNode;
				temp = temp->next;
			}
			else
			{
				break;
			}
		}
	}
}

尾插法建立单链表时,初始结点需要单独处理,代码中定义了一个 f l a g {\rm flag} flag用于标识是否为第一个结点。 t e m p {\rm temp} temp待插入的新结点, t e m p {\rm temp} temp始终指向单链表的尾结点。输入- 1 {\rm 1} 1时,表示单链表创建完成。同时,尾插法建立的单链表与输入元素成顺序关系。如输入为 1   2   3 {\rm 1\ 2\ 3} 1 2 3 - 1 {\rm 1} 1,则建立的单链表为 1 → 2 → 3 → N U L L {\rm 1→2→3→NULL} 123NULL


2. 遍历链表

顺序遍历单链表采用迭代方法,逆序遍历单链表采用递归方法。

顺序遍历 C {\rm C} C++代码:

void visitLinkList(ListNode* &head) {
	if (head == NULL) {
		return;
	}
	else
	{
		while (head != NULL)
		{
			cout << head->val;
			head = head->next;
		}
	}
}

逆序遍历 C {\rm C} C++代码:

void visitLinkList(ListNode* &head) {
	if (head == NULL) {
		return;
	}
	else
	{
		visitLinkList(head->next);
		cout << head->val;
	}
}

3. 翻转链表

翻转链表即将链表的值逆序,这里采用迭代和递归的方法翻转链表。 C {\rm C} C++代码:

C {\rm C} C++代码一:

ListNode* reverseLinkList(ListNode* &head) {
	if (head == NULL) {
		return head;
	}
	ListNode *temp = head->next;
	head->next = NULL;
	ListNode *t;
	while (temp)
	{
		t = temp->next;
		temp->next = head;
		head = temp;
		temp = t;
	}
	return head;
}

迭代法翻转链表时,首先将首结点取下,然后将剩余结点依次按照头插法依次插入即可完成翻转链表。

C {\rm C} C++代码二:

ListNode* reverseLinkList(ListNode* &head) {
	if (head == NULL || head->next == NULL) {
		return head;
	}
	else
	{
		ListNode* temp = reverseLinkList(head->next);
		head->next->next = head;
		head->next = NULL;
		return temp;
	}
}

递归法翻转链表时,首先遍历到链表尾,然后根据递归的性质从后往前依次改变结点的 n e x t {\rm next} next指针,最后返回 t e m p {\rm temp} temp为首结点的链表。


4. 插入、删除结点

插入

假如插入函数的功能是:在一个值有序的单链表中插入元素,使插入后的链表仍然有序。

C {\rm C} C++代码:

void insertLinkList(ListNode* &head, int val) {
	if (head == NULL) {
		ListNode* head = new ListNode(val);
		return;
	}
	else
	{
		ListNode* pre = head;
		ListNode* temp = pre->next;
		while (temp && val > temp->val) {
			pre = pre->next;
			temp = temp->next;
		}
		ListNode* L = new ListNode(val);
		L->next = pre->next;
		pre->next = L;
	}
}

在单链表中插入结点时,首先定义 t e m p {\rm temp} temp指针遍历单链表,同时设置其前驱指针 p r e {\rm pre} pre w h i l e {\rm while} while循环用于寻找合适的插入位置,将结点插入到 p r e {\rm pre} pre t e m p {\rm temp} temp之间。然后执行如下插入操作:
在这里插入图片描述

图3:插入结点

删除

假设删除函数功能为:删除值为 v a l {\rm val} val的结点(假设该值总是存在)。

C {\rm C} C++代码:

void deleteLinkList(ListNode* &head, int val) {
	if (head == NULL) {
		return;
	}
	else
	{
		ListNode* pre = head;
		ListNode* temp = pre->next;
		while (temp && val != temp->val)
		{
			pre = pre->next;
			temp = temp->next;
		}
		pre->next = temp->next;
		free(temp);
	}
}

在单链表中删除结点时,首先定义 t e m p {\rm temp} temp指针遍历单链表,同时设置其前驱指针 p r e {\rm pre} pre w h i l e {\rm while} while循环用于寻找合适的删除位置,将结点 t e m p {\rm temp} temp删除。然后执行如下删除操作:
在这里插入图片描述

图4:删除结点


5. 合并链表

输入两个有序链表 A {\rm A} A B {\rm B} B(假设两者均不为空),合并后的结果为以 C {\rm C} C为首结点的链表。

C {\rm C} C++代码:

void mergeLinkList(ListNode* &A, ListNode* &B, ListNode* &C) {
	ListNode* p = A;
	ListNode* q = B;
	if (p -> val >= q->val) {
		C = B;
		q = q->next;
	}
	else
	{
		C = A;
		p = p->next;
	}
	C->next = NULL;
	ListNode* r = C;
	while (p && q)
	{
		if (p->val <= q->val) {
			r->next = p;
			p = p->next;
			r = r->next;
		}
		else
		{
			r->next = q;
			q = q->next;
			r = r->next;
		}
	}
	if (q) {
		r->next = q;
	}
	if (p) {
		r->next = p;
	}
}

首先将 C {\rm C} C指向值较小的结点,定义一个指针 r {\rm r} r用于接收结点。然后遍历两个链表,依次将较小值插入 r {\rm r} r的末尾。最后,将非空的内容连接在 r {\rm r} r后面。如果要将结果保存为非递增的顺序,改动地方为:(1) i f {\rm if} if的判断条件;(2)最后非空的内容连接方式。


6. 拆分链表

设存在单链表 A = { a 1 , b 1 , a 2 , . . . , a n , b n } {\rm A}=\{{\rm a_1,b_1,a_2,...,a_n,b_n}\} A={a1,b1,a2,...,an,bn},拆分函数的功能是将 A {\rm A} A拆分为两个链表 B {\rm B} B C {\rm C} C,其中满足 B = { a 1 , a 2 , . . . , a n } {\rm B}=\{{\rm a_1,a_2,...,a_n}\} B={a1,a2,...,an} C = { b n , . . . , b 2 , b 1 } {\rm C}=\{{\rm b_n,...,b_2,b_1}\} C={bn,...,b2,b1}

依次将链表 A {\rm A} A上的结点接在 B {\rm B} B C {\rm C} C上即可,其中需要注意 B {\rm B} B C {\rm C} C中元素的顺序。则在生成 B {\rm B} B时采用尾插法,在生成 C {\rm C} C时采用头插法。这里假设 A {\rm A} A不为空。

C {\rm C} C++代码:

void splitLinkList(ListNode* &A, ListNode* &B, ListNode* &C) {
	ListNode* ra = A;
	ListNode* rb = NULL;
	ListNode* pc = NULL;
	ListNode* t;
	int flag = true;
	while (ra)
	{
		if (flag) {
			rb = ra;
			ra = ra->next;
			if (ra == NULL) {
				break;
			}
			pc = ra;
			ra = ra->next;
			pc->next = NULL;		
			flag = false;
			B = rb;
			continue;
		}
		rb->next = ra;
		rb = ra;
		ra = ra->next;
		if (ra == NULL) {
			break;
		}
		t = ra->next;
		ra->next = pc;
		pc = ra;
		ra = t;
	}
	rb->next = NULL;
	C = pc;
}

定义结点 r a {\rm ra} ra r b {\rm rb} rb p c {\rm pc} pc分别处理链表 A {\rm A} A B {\rm B} B C {\rm C} C,其中结点 t {\rm t} t是头插法的辅助结点。定义标志 f l a g {\rm flag} flag单独处理 A {\rm A} A B {\rm B} B的首结点。


7. 链表的其他操作

(一)删除有序链表的重复元素
现在假设一个单链表存储的内容是一个有序且含有重复值的序列,该函数实现删除重复元素的功能。例如:原链表为 1 → 2 → 3 → 3 → 3 → 4 → 4 → 7 → 7 → 9 → 9 → N U L L {\rm 1→2→3→3→3→4→4→7→7→9→9→NULL} 12333447799NULL,删除后的链表为 1 → 2 → 3 → 4 → 7 → 9 → N U L L {\rm 1→2→3→4→7→9→NULL} 123479NULL

C {\rm C} C++代码一:

void deleteRepeatedLinkList(ListNode* &head) {
	if (head) {
		ListNode* q;
		ListNode* p = head;
		while (p ->next != NULL)
		{
			if (p->val == p->next->val) {
				q = p->next;
				p->next = p->next->next;
				free(q);
			}
			else
			{
				p = p->next;
			}
		}
	}
}

代码一思想比较常规,直接遍历链表。如果当前结点的值等于下一个结点的值,则改变指针并释放重复结点。

针对序列 1 → 2 → 3 → 3 → 3 → 4 → 4 → 7 → 7 → 9 → 9 → N U L L {\rm 1→2→3→3→3→4→4→7→7→9→9→NULL} 12333447799NULL,可以将非重复的元素不断往前移动,得到结果 1 → 2 → 3 → 4 → 7 → 9 → 4 → 7 → 7 → 9 → 9 → N U L L {\rm 1→2→3→4→7→9→4→7→7→9→9→NULL} 12347947799NULL,然后将后续结点释放,得到 1 → 2 → 3 → 4 → 7 → 9 → N U L L {\rm 1→2→3→4→7→9→NULL} 123479NULL

C {\rm C} C++代码二:

void deleteRepeatedLinkList_(ListNode* &head) {
	if (head) {
		ListNode* r;
		ListNode* p = head;
		ListNode* q = p->next;
		while (q)
		{
			while (q && p->val == q->val)
			{
				q = q->next;
			}
			if (q) {
				p = p->next;
				p->val = q->val;
			}
		}
		q = p->next;
		p->next = NULL;
		while (q)
		{
			r = q;
			q = q->next;
			free(r);
		}
	}
}

最外层 w h i l e {\rm while} while用于遍历链表,里层第一个 w h i l e {\rm while} while循环用于寻找使得 q {\rm q} q指向结点的值不等于 p {\rm p} p指向结点的值。算法过程中,指针 p {\rm p} p始终指向链表的尾。最后,使用辅助结点 r {\rm r} r释放掉重复的元素。

(二)链表的直接插入排序算法
单链表由于其数据结构的特点,不适合做大规模的移动类和交换类的排序算法。这里仅给出单链表的直接插入排序算法:给定一个序列 4 → 2 → 1 → 5 → 7 → 6 → 9 → N U L L {\rm 4→2→1→5→7→6→9→NULL} 4215769NULL,排序后得到结果为 1 → 2 → 4 → 5 → 6 → 7 → 9 → N U L L {\rm 1→2→4→5→6→7→9→NULL} 1245679NULL。为了便于操作,这里假设单链表是带头结点的。

C {\rm C} C++代码:

inline void selectSortLinkList(ListNode* head) {
	ListNode* pre;
	ListNode* p = head->next;
	ListNode* q = p->next;
	p->next = NULL;
	p = q;
	while (p)
	{
		q = p->next;
		pre = head;
		while (pre->next != NULL && pre->next->val < p->val) {
			pre = pre->next;
		}
		p->next = pre->next;
		pre->next = p;
		p = q;
	}
}

这里结点 p r e {\rm pre} pre用于寻找合适的插入位置,最终结点的插入位置为 p r e → n e x t {\rm pre→next} prenext。结点 q {\rm q} q为了保持链表不断链。最外层循环用于遍历单链表,里层的 w h i l e {\rm while} while循环用于寻找合适的插入为位置。接着执行插入操作。

(三)两个链表的公共结点/共同后缀
在这里插入图片描述

图5:公共结点

两个链表的公共结点,也就是两个链表从某一个结点开始,它们的 n e x t {\rm next} next指针指向的内容都相同(由于每个结点只有一个 n e x t {\rm next} next域,如果当前结点的相同的后面结点也相同。即公共结点中相同元素存放在同一位置)。两个结点有公共结点在拓扑形状上为 “ Y ” {\rm “Y”} Y。首先,很容易想到的方法是暴力求解。遍历第一个链表,如果然后从第二个链表上面寻找是否有相同的结点,这种方法的时间复杂度为 O ( m n ) {\rm O(mn)} O(mn)

C {\rm C} C++代码一:

ListNode* commonLinkList(ListNode* &A, ListNode* &B) {
	if (A == NULL || B == NULL) {
		return NULL;
	}
	ListNode* b = B;
	while (A) {
		while (b)
		{
			if (b != A) {
				b = b->next;
			}
		}
		A = A->next;
	}
	return A;
}

由上述代码可知,暴力法会产生很多重复的操作。我们知道,公共结点开始的位置后面链表的长度是一致的。我们可以先让两个遍历链表的指针同步,然后同时向后移动用以寻找公共结点。

C {\rm C} C++代码二:

inline ListNode* commonLinkList(ListNode* &A, ListNode* &B) {
	if (A == NULL || B == NULL) {
		return NULL;
	}
	int len1 = Length(A);
	int len2 = Length(B);
	ListNode* longList;
	ListNode* shortList;
	int dist;
	if (len1 > len2) {
		longList = A;
		shortList = B;
		dist = len1 - len2;
	}
	else
	{
		longList = B;
		shortList = A;
		dist = len2 - len1;
	}
	while (dist--)
	{
		longList = longList->next;
	}
	while (longList)
	{
		if (longList == shortList) {
			return longList;
		}
		else
		{
			longList = longList->next;
			shortList = shortList->next;
		}
	}
	return longList;
}

L e n g t h {\rm Length} Length函数用于求链表的长度,定义两个指针 l o n g L i s t {\rm longList} longList s h o r t L i s t {\rm shortList} shortList分别用于遍历较短和较长的链表,变量 d i s t {\rm dist} dist用于计算两链表的长度差。第一个 w h i l e {\rm while} while循环使两个指针同步,第二个 w h i l e {\rm while} while循环用于寻找公共结点。此时,算法的时间复杂度为 O ( l e n 1 + l e n 2 ) {\rm O(len1+len2)} O(len1+len2)

此外,还可以借助额外辅助空间的方法。公共结点的值一定是从后往前依次相等的。我们可以定义两个栈分别用于存放两个链表的元素值,然后依次出栈,最后一个元素值相等处即为公共结点的位置。

C {\rm C} C++代码三:

ListNode* commonLinkList(ListNode* &A, ListNode* &B) {
	if (A == NULL || B == NULL) {
		return NULL;
	}
	stack<ListNode*> s1;
	stack<ListNode*> s2;
	while (A)
	{
		s1.push(A);
		A = A->next;
	}while (B)
	{
		s2.push(B);
		B = B->next;
	}
	ListNode* common = NULL;
	while (!s1.empty() && !s2.empty())
	{
		ListNode* temp1 = s1.top();
		ListNode* temp2 = s2.top();
		if (temp1->val == temp2->val) {
			common = temp1;
			s1.pop();
			s2.pop();
		}
		else
		{
			break;
		}
	}
	return common;
}

(四)两个链表元素的交集
假设两个链表的元素按非递减顺序存放,该函数的功能是求两个链表元素的交集。为了便于操作,这里假设单链表是带头结点的。

C {\rm C} C++代码:

ListNode* intersectionLinkList(ListNode* &A, ListNode* &B) {
	ListNode* p = A->next;
	ListNode* q = B->next;
	ListNode* c = A;
	ListNode* u;
	while (p && q) {
		if (p->val == q->val) {
			c->next = p;
			c = p;
			p = p->next;
			u = q;
			q = q->next;
			free(u);
		}
		else if(p->val < q->val)
		{
			u = p;
			p = p->next;
			free(u);
		}
		else
		{
			u = q;
			q = q->next;
			free(u);
		}
	}
	while (p)
	{
		u = p;
		p = p->next;
		free(u);
	}
	while (q)
	{
		u = q;
		q = q->next;
		free(u);
	}
	c->next = NULL;
	return A;
}

A {\rm A} A的头结点作为最后的返回结果。指针 p {\rm p} p和指针 q {\rm q} q分别用于遍历链表 A {\rm A} A和指针 B {\rm B} B。然后,将链表 A {\rm A} A和指针 B {\rm B} B中剩余元素释放掉。最后将结果链表的 n e x t {\rm next} next指针置为空,返回结果。

(五)从小到大删除链表中的元素
该函数功能为从小到大删除链表中的元素,直至链表为空。为了便于操作,这里假设单链表是带头结点的。

C {\rm C} C++代码:

void deleteSmallToLargeLinkList(ListNode* &head) {
	ListNode* premin, *min;
	ListNode* pre, *p;
	premin = pre = head;
	min = premin->next;
	p = pre->next;
	while (p)
	{
		while (p)
		{
			if (min->val > p->val) {
				premin = pre;
				min = p;
			}
			pre = p;
			p = p->next;
		}
		Visit(min->val);
		premin->next = min->next;
		free(min);
		premin = pre = head;
		min = premin->next;
		p = pre->next;
	}
}

采用指针 p r e m i n {\rm premin} premin m i n {\rm min} min共同指向最小值的位置,指针 p r e {\rm pre} pre p {\rm p} p用于遍历链表。找到最小值后删除,并重置上述 4 {\rm 4} 4个指针的位置。上述程序的时间复杂度为 O ( n 2 ) {\rm O(n^2)} O(n2)。另外,我们可以先将链表中元素依次赋值到数组中,然后在数据中以时间复杂度为 O ( l o g 2 n ) {\rm O(log_2n)} O(log2n)的算法排序,最后再放回原数组。这是一种典型的用空间换时间的思想。

(六)查找链表倒数第k个位置的元素
该函数的功能是找到链表中倒数第 k {\rm k} k个元素,如果该元素存在,访问该元素并返回1;否则返回0。该函数的思想是:定义两个指针 p {\rm p} p q {\rm q} q,初始状态下都指向首结点。然后先让 q {\rm q} q移动   k {\ k}  k步后,两个指针同步移动。当 q {\rm q} q为空结点时则结点 p {\rm p} p指向的为倒数第 k {\rm k} k个结点。

C {\rm C} C++代码:

int lastedKLinkList(ListNode* head, int k) {
	ListNode*p, *q;
	p = q = head;
	int count = 0;
	while (q)
	{
		if (count < k) {
			count++;
		}
		else
		{
			p = p->next;
		}
		q = q->next;
	}
	if (count < k) {
		return 0;
	}
	else {
		cout << p->val << endl;
		return 1;
	}
}

首先定义快指针 q {\rm q} q和慢指针 p {\rm p} p,在循环体内部,如果 c o u n t < k {\rm count<k} count<k则只移动快指针,否则当 c o u n t = k {\rm count=k} count=k时两个指针均移动。最后判断,链表的长度是否长于等于所给 k {\rm k} k值,并返回结果。


8. 单链表操作的总结

单链表操作算法的形式类似,但设计方法多变。在拿到一个题后,往往现在纸上手动模拟程序的运行过程,然后书写代码。特别注意的是,合理地保存下一个结点防止断链。此外,单链表不适合做排序操作。要对其中元素进行排序时,可以考虑借助数组以空间换时间的思想。最后,在单链表中经常出现的一种思想是:快慢指针,定义两个移动速度不同或移动位置不同的指针有时可以大大简化算法流程。如第7节中的(三)和(六)等。


参考

  1. 率辉. 2019版数据结构高分笔记[M]. 北京:机械工业出版社. 2018.1.
  2. 王道论坛. 2019年数据结构考研复习指导[M]. 北京:电子工业出版社, 2018.4.
  3. https://www.cnblogs.com/edisonchou/p/4822675.html.


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
排序作业 选择题(每题2分,共22分)。 1.若表R在排序前已按键值递增顺序排列,则(   )算法的比较次数最少。 A.直接插入排序            B.快速排序     C.归并排序                D.选择排序 2.对各种内部排序方法来说,(   )。 A.快速排序时间性能最佳                             B.归并排序是稳定的排序方法 C.快速排序是一种选择排序                          D.堆排序所用的辅助空间比较大 3.  排序算法的稳定性是指(   )。 A.经过排序之后,能使值相同的数据保持原顺序中的相对位置不变。 B.经过排序之后,能使值相同的数据保持原顺序中的绝对位置不变。 C.排序算法的性能与被排序元素的数量关系不大 D.排序算法的性能与被排序元素的数量关系密切 4. 如下序列中,(   )序列是大顶堆。 A.  {4,5,3,2,1}               B.  {5,3,4,1,2}        C.  {1,2,3,4,5}               D.  {1,2,3,5,4} 5. 若将{3,2,5,4,1}排为升序,则实施快速排序一趟后的结果是(   )(其中,枢轴记录取首记录)。 A.  {1,2,3,4,5}                  B.  {1,2,4,5,3}        C.  {1,3,5,4,2}                  D.  {2,5,4,1,3} . 若将{1,2,3,4,5,6,7,9,8}排为升序,则(   )排序方法的“比较记录”次数最少。 A.  快速排序                   B.  简单选择排序     C.  直接插入排序               D.  冒泡排序 7. 若将{5,4,3,2,1}排为升序,则(   )排序方法的“移动记录”次数最多。 A.  快速排序                                B.  冒泡排序 C.  直接插入排序                       D.  简单选择排序 8. 用简单选择排序将顺序表{2,3,1 ,3′,2′}排为升序,实施排序1趟后结果是{1 ,3,2 ,3′,2′},则排序3趟后的结果是(   )。 A.  {1 ,2,3 ,3′,2′}                       B.  {1 ,2 ,2′,3 ,3′} C.  {1 ,2′,2 ,3 ,3′}                      D.  {1 ,2 ,2′,3′,3 } 9.下列排序算法中,(    )排序在某趟结束后不一定选出一个元素放到其最终的位置上。 A.选择             B.冒泡           C.归并           D.堆 10.下列排序算法中,稳定的排序算法是(  )。 A.堆排序                B.直接插入排序   C.快速排序              D.希尔排序 11.堆排序的时间复杂度是(    )。 A.O(n*n)                 B.O(n*log n)       C.O(n)                   D.O(log n) 填空题(每空4分,共4分)。 对n个元素进行归并排序,空间复杂度为         。 综合题(共24分)。 1. (共12分)有一组待排序的关键字如下: (54,38,96,23,15,72,60,45,83) 分别写出希尔排序(d=5)、快速排序、堆排序、归并排序第一趟升序排序后的结果(其中堆排序的第一趟指序列完成初始建堆、将堆顶元素置为最末位置后其余元素调整为堆的结果)(每个3分)。 希尔排序:   快速排序: 堆排序: 归并排序:  2. (共12分)已知数据序列为(12,5,9,20,6,31,24),对该项数据序列进行排序,分别写出直接插入排序、简单选择排序、快速排序、堆排序、二路归并排序及基数排序第一趟升序排序结果(其中堆排序的第一趟指序列完成初始建堆、将堆顶元素置为最末位置后其余元素调整为堆的结果)(每个2分)。 直接插入排序: 简单选择排序: 快速排序: 堆排序: 二路归并排序: 基数排序:    

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值