【声明】并非原创,根据网上帖子中的讨论整理而成。
源帖地址:http://bbs.csdn.net/topics/390308007
【楔子】
一个好的问题往往能引起大家不论从深度上还是广度上的各种思维、各路讨论,所谓百家争鸣 百花齐放 思维爆炸 头脑风暴就是如此。
所以有时候问题的答案不重要,重要的是思考问题的过程。
【正文】
题目:数组中有重复数据,求出每个数据出现的次数并按照次数的由大到小排列出来
1 LZ给出常规思路-二维数组
public class TwoArrays {
public int[][] timesSort(int array[]){
if(null == array){
return null;
}
int arrayTwo[][] = new int[array.length][2];
//二维数组赋值
for(int i=0; i<array.length; i++){
arrayTwo[i][0] = array[i];
int count = 0;
for (int j = 0; j < array.length; j++) {
if (array[i] == array[j]) {
count++;
}
}
arrayTwo[i][1] = count;
}
//冒泡排序
for(int i=0; i<array.length -1; i++){
for(int j=0; j < array.length - i -1; j++){
int[] temp = new int[2];
if(arrayTwo[j][1] < arrayTwo[j+1][1]){
temp = arrayTwo[j];
arrayTwo[j] = arrayTwo[j+1];
arrayTwo[j+1] = temp;
}
}
}
return arrayTwo;
}
public static void main(String[] args) {
int array[] = {2, 5, 6, 2, 4, 3, 5, 4, 2, 5, 2, 4, 2, 6, 3, 5, 4, 2,
3, 5, 6, 5, 2, 4, 2, 5, 2, 3, 5, 2, 3, 5, 2, 3, 5, 2, 3, 5, 2,
7, 8, 8, 7, 8, 7, 9, 0};
TwoArrays two = new TwoArrays();
int[][] array2 = two.timesSort(array);
// int size = array2.length / array2[0].length;
System.out.println("start-------->");
for(int i=0; i<array.length; i++){
System.out.println("data: " + array2[i][0] + "; count: " + array2[i][1]);
}
System.out.println("Game Over=================");
}
}
2 达人粗线
网友 SmallYamateh
改变你的数据存储方式,不要仅仅局限于for循环、冒泡之类的线性算法,并且把“面向”对象的思想融入编程。
以下方法,算法的平均复杂度为O(Nlog2N)。
中心思想:遍历数组,依次插入到二叉排序树,如果找到数字相等的,节点的count++;中序遍历算法,输出这个二叉排序树。(PS:如果想得到标准的O(Nlog2N)算法,可以通过旋转树枝的办法,减小树的高度,把二叉排序树优化为平衡二叉树。)
节点类:
/**
* 二叉树节点类
*/
public class TreeNode {
private int data; //关键字
private TreeNode leftCh; //左子树
private TreeNode rightCh; //右子树
private int count; //关键字出现的次数
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
public TreeNode getLeftCh() {
return leftCh;
}
public void setLeftCh(TreeNode leftCh) {
this.leftCh = leftCh;
}
public TreeNode getRightCh() {
return rightCh;
}
public void setRightCh(TreeNode rightCh) {
this.rightCh = rightCh;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public TreeNode(int data){
this.data = data;
this.count = 1;
}
public TreeNode(int data, TreeNode leftCh, TreeNode rightCh){
this.data = data;
this.leftCh = leftCh;
this.rightCh = rightCh;
this.count = 1;
}
public TreeNode(){
super();
}
/**
* 为了便于调试和输出
*/
public String toString(){
return "数字=" + data + ", 出现次数=" + count +";";
}
}
二叉排序树的类:
/**
* 二叉排序树的类
* excellent Sample
* 算法的平均复杂度为O(Nlog2N)
* 改变你的数据存储方式,不要仅仅局限于for循环、冒泡之类的线性算法,并且把“面向”对象的思想融入编程。
*/
public class TreeSort {
private TreeNode root;
public TreeNode getRoot() {
return root;
}
public void setRoot(TreeNode root) {
this.root = root;
}
/**
* 主方法
* @param array
*/
public void createTreeAndOutput(int[] array){
//遍历数组 同时插入节点到二叉树
for(int data:array){
this.insertNode(data);
}
//中序输出这个排序树
this.middleOrder(root);
}
/**
* 中序遍历 递归算法
* @param p
*/
public void middleOrder(TreeNode p){
if(null != p){
this.middleOrder(p.getLeftCh());
System.out.println(p);
this.middleOrder(p.getRightCh());
}
}
/**
* 1 先看有无根节点,没有则先创建
* 2 指向根节点,开始循环插入过程
* 3 如果key值大于data则插入左子树,小于插入右子树。相等则不插入count加1
* 4 遍历过程中如果左(右)子树为空 则 可直接插入,否则继续遍历
* @param data
*/
public void insertNode(int data){
//如果没有根节点 则创造新的根节点
if(null == this.getRoot()){
root = new TreeNode(data);
this.setRoot(root);
}else{
//初始化指针p 指向根节点
TreeNode p = root;
while(true){
//如果key值大于data 则插入左子树
if(p.getData() > data){
//如果p没有左子树 则直接插入到p的左子树
if(null == p.getLeftCh()){
TreeNode node = new TreeNode(data);
p.setLeftCh(node);
break;
}else{
//如果p有左子树 则让指针指向p的左子树 继续遍历
p = p.getLeftCh();
continue;
}
//如果key值小于data 则插入右子树 和左子树情况类似
}else if(p.getData() < data){
if(null == p.getRightCh()){
TreeNode node = new TreeNode(data);
p.setRightCh(node);
break;
}else{
p = p.getRightCh();
continue;
}
//key值相等,不必插入新的节点 count加1
}else{
int count = p.getCount();
count++;
p.setCount(count);
break;
}
}
}
}
public static void main(String[] args) {
TreeSort treeSort = new TreeSort();
int[] array = {2, 5, 6, 2, 4, 3, 5, 4, 2, 5, 2, 4, 2, 6, 3, 5, 4, 2,
3, 5, 6, 5, 2, 4, 2, 5, 2, 3, 5, 2, 3, 5, 2, 3, 5, 2, 3, 5, 2,
7, 8, 8, 7, 8, 7, 9, 0};
System.out.println("Come on ------------------------>");
treeSort.createTreeAndOutput(array);
System.out.println("Game Over!!!");
}
}
网友表示碉堡了,然而有明白的网友出来吐槽: 原来是按数的大小排序出来的,不是数字出现的次数。
达人就是达人稍事休息立马给出了升级算法:
3
思路类似,但是复杂变大了,多了一个“以出现次数为关键字的二叉排序树”,这颗二叉排序树的节点包含两个重要信息“出现次数,一群数字的链表”。
改进了一下一开始的方法,中心思想“遍历第一个二叉树的同时,向第二颗二叉排序树插叶子”。
升级二叉树节点类
package cn.demo.data;
/**
*以 出现次数 为关键字的节点 本身包含一个链表 插入的方法为头插法
*/
public class CountNode {
private int count; //出现次数
private SimpleNode first; //链表表头结点
private CountNode leftCh; //左子树
private CountNode rightCh; //右子树
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public SimpleNode getFirst() {
return first;
}
public void setFirst(SimpleNode first) {
this.first = first;
}
public CountNode getLeftCh() {
return leftCh;
}
public void setLeftCh(CountNode leftCh) {
this.leftCh = leftCh;
}
public CountNode getRightCh() {
return rightCh;
}
public void setRightCh(CountNode rightCh) {
this.rightCh = rightCh;
}
public CountNode(int count, SimpleNode first){
this.count = count;
this.first = first;
}
public CountNode(int count){
this.count = count;
}
public CountNode(){
super();
}
public String toString(){
return "CountNode [count=" + count + "]";
}
}
countNode里边的链表所包含的节点类
package cn.demo.data;
/**
* 链表结点类 只包含next指针 和 数字域
*/
public class SimpleNode {
private int data; //数字
private SimpleNode next; //链表的下一个结点
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
public SimpleNode getNext() {
return next;
}
public void setNext(SimpleNode next) {
this.next = next;
}
public SimpleNode(int data){
this.data = data;
}
public SimpleNode(){
super();
}
public String toString(){
return "SimpleNode [数字=" + data + "]";
}
}
改进后的二叉树类:改进了一下一开始的方法,中心思想“遍历第一个二叉树的同时,向第二颗二叉排序树插叶子”
import org.junit.Test;
/**
* 二叉排序树的类
*
*/
public class TreeInsert {
/**
* 根节点
*/
private TreeNode root;
public TreeNode getRoot() {
return root;
}
public void setRoot(TreeNode root) {
this.root = root;
}
/**
* 总的方法
*/
public void createTreeAndOutPut(int[] array){
//只需遍历一次数组 遍历的同时插入节点到排序树
for(int data:array){
this.insertNode(data);
}
//中序遍历这个二叉排序树
this.middleOrder(root);
//按照出现次数输出所有数字
middleOrderCountTree(this.getCountRoot());
}
private CountNode countRoot;
public CountNode getCountRoot() {
return countRoot;
}
public void setCountRoot(CountNode countRoot) {
this.countRoot = countRoot;
}
/**
* 插入单个countNode的方法
*/
public void insertCountNode(TreeNode node){
int count=node.getCount();
//如果没有根节点 创造新的根节点
if(this.getCountRoot()==null){
countRoot=new CountNode(count);
SimpleNode sn=new SimpleNode(node.getData());
//设置这个链表的头结点指针
countRoot.setFirst(sn);
}else{
//初始化指针p指向根节点
CountNode p=this.getCountRoot();
while(true){
//如果p的data小于data
if(p.getCount()<count){
//如果p没有左子树 则直接插入到p的左子树
if(p.getLeftCh()==null){
CountNode leftCh=new CountNode(count);
//由于节点是新建的 所以它的链表也是空的,因此要设置链表的头指针
SimpleNode sn=new SimpleNode(node.getData());
leftCh.setFirst(sn);
p.setLeftCh(leftCh);
break;
}else{
//如果p有左子树 则让指针指向p的左子树 继续遍历
p=p.getLeftCh();
continue;
}
}
//插入右子树的情况 和左子树类似
else if(p.getCount()>count){
if(p.getRightCh()==null){
CountNode rightCh=new CountNode(count);
SimpleNode sn=new SimpleNode(node.getData());
rightCh.setFirst(sn);
p.setRightCh(rightCh);
break;
}else{
p=p.getRightCh();
continue;
}
}else{
//找到count的值相等的 不必插入新的节点,但要用头插法把节点插入到所含链表的头部
SimpleNode first=p.getFirst();
SimpleNode sn=new SimpleNode(node.getData());
sn.setNext(first);
//设置头指针
p.setFirst(sn);
break;
}
}
}
}
/**
* 中序遍历 递归算法 p为当前节点的指针
*/
public void middleOrder(TreeNode p){
if(p!=null){
//遍历左子树
this.middleOrder(p.getLeftCh());
//遍历本次节点的同时,插入到“以出现次数为关键字的二叉排序树”
this.insertCountNode(p);
//遍历右子树
this.middleOrder(p.getRightCh());
}
}
/**
* 中序遍历 递归算法 输出
*/
public void middleOrderCountTree(CountNode p){
if(p!=null){
//遍历左子树
this.middleOrderCountTree(p.getLeftCh());
//遍历本次节点 即遍历整个链表
SimpleNode sp=p.getFirst();
System.out.println("出现次数为"+p.getCount()+"的数字有:");
while(sp!=null){
System.out.print(" "+sp.getData());
sp=sp.getNext();
}
System.out.println();
//遍历右子树
this.middleOrderCountTree(p.getRightCh());
}
}
/**
* 插入单个节点的方法
*/
public void insertNode(int data){
//如果没有根节点 创造新的根节点
if(this.getRoot()==null){
root=new TreeNode(data);
}else{
//初始化指针p指向根节点
TreeNode p=root;
while(true){
//如果p的data小于data
if(p.getData()<data){
//如果p没有左子树 则直接插入到p的左子树
if(p.getLeftCh()==null){
TreeNode leftCh=new TreeNode(data);
p.setLeftCh(leftCh);
break;
}else{
//如果p有左子树 则让指针指向p的左子树 继续遍历
p=p.getLeftCh();
continue;
}
}
//插入右子树的情况 和左子树类似
else if(p.getData()>data){
if(p.getRightCh()==null){
TreeNode rightCh=new TreeNode(data);
p.setRightCh(rightCh);
break;
}else{
p=p.getRightCh();
continue;
}
}else{
//设置重复的情况 不必插入新的节点,直接让p的count加1
int count=p.getCount();
count++;
p.setCount(count);
break;
}
}
}
}
/**
* 用于测试的类 为了程序的健壮性,我们多生成些随机数放入数组
*/
@Test
public void test(){
TreeInsert ti=new TreeInsert();
int[] array={2, 5, 6, 2, 4, 3, 5, 4, 2, 5, 2, 4, 2, 6, 3, 5, 4, 2,
3, 5, 6, 5, 2, 4, 2, 5, 2, 3, 5, 2, 3, 5, 2, 3, 5, 2, 3, 5, 2,
7, 8, 8, 7, 8, 7, 9, 0 };
ti.createTreeAndOutPut(array);
}
}
4
以上的达人的方法,众网友纷纷表示钦佩。本屌根据达人的例子按自己的思路稍微修改了一下
从两颗二叉树变成了一棵二叉树和一个二维数组。
上酸菜:
package cn.demo.data;
/**
* 二叉排序树的类
* TreeSort升级版
* 在别人原有思路上有所创新 才有价值。
* Super Sample
*/
public class TreeSort2 {
private CountNode countRoot;
public CountNode getCountRoot() {
return countRoot;
}
public void setCountRoot(CountNode countRoot) {
this.countRoot = countRoot;
}
/*
* 主处理方法
*/
public void handle(int[] array){
//1 生成二维数组
int[][] array2 = this.getTwoDivArray(array);
//2 创建并输出二叉树
this.createTreeAndOutput(array2);
}
/*
* 传入一维数组,生成二维数组
* array[data][count]
*/
public int[][] getTwoDivArray(int[] array){
if(null == array){
return null;
}
int[][] array2 = new int[array.length][2];
//二维数组赋值
for(int i=0; i<array.length; i++){
array2[i][0] = array[i];
int count = 0;
for (int j = 0; j < array.length; j++) {
if (array[i] == array[j]) {
count++;
}
}
array2[i][1] = count;
}
return array2;
}
/*
* 遍历数组 同时插入节点到二叉树
*/
public void createTreeAndOutput(int[][] array){
//遍历数组 同时插入节点到二叉树
for(int[] arr : array){
int count = arr[1];
int data = arr[0];
this.insertCountNode(count, data);
}
//中序输出这个排序树
this.middleOrderCountTree(this.getCountRoot());
}
/**
* 核心方法 插入节点至二叉树
* @param count 出现次数
* @param data 数值
* 2012-12-18
*/
public void insertCountNode(int count, int data){
//如果没有根节点 则创造新的根节点
if(null == this.getCountRoot()){
countRoot = new CountNode(count);
SimpleNode sn = new SimpleNode(data);
//设置链表的头结点指针
countRoot.setFirst(sn);
}else{
//初始化指针p 指向根节点
CountNode p = this.getCountRoot();
while(true){
//如果key值大于data 则插入左子树
if(p.getCount() > count){
//如果p没有左子树 则直接插入到p的左子树
if(null == p.getLeftCh()){
CountNode node = new CountNode(count);
SimpleNode sn = new SimpleNode(data);
node.setFirst(sn);
p.setLeftCh(node);
break;
}else{
//如果p有左子树 则让指针指向p的左子树 继续遍历
p = p.getLeftCh();
continue;
}
//如果key值小于data 则插入右子树 和左子树情况类似
}else if(p.getCount() < count){
if(null == p.getRightCh()){
CountNode node = new CountNode(count);
SimpleNode sn = new SimpleNode(data);
node.setFirst(sn);
p.setRightCh(node);
break;
}else{
p = p.getRightCh();
continue;
}
//找到count的值相等的 不必插入新的节点,但要用头插法把节点插入到所含链表的头部
}else{
SimpleNode first = p.getFirst();
SimpleNode snode = first;
boolean flag = false;
while(null != snode){
if(data == snode.getData()){
flag = true;
break;
}
snode = snode.getNext();
}
//链表中已经存在的结点不插入
if(!flag){
SimpleNode sn = new SimpleNode(data);
//头插法
sn.setNext(first);
p.setFirst(sn);
}
break;
}
}
}
}
/*
* 中序遍历 递归算法 输出
*/
public void middleOrderCountTree(CountNode p){
if(null != p){
//遍历左子树
this.middleOrderCountTree(p.getLeftCh());
//遍历本次结点 即遍历整个链表
SimpleNode sp = p.getFirst();
System.out.println("出现次数为"+p.getCount()+"的数字有:");
while(null != sp){
System.out.print(" "+sp.getData());
sp = sp.getNext();
}
System.out.println();
//遍历右子树
this.middleOrderCountTree(p.getRightCh());
}
}
}
5 还有网友提供了集合框架和泛型的思路
5.1 普通HashMap
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class MapSort {
public Map<Integer, Integer> getCountSortMap(int[] array){
if(null == array){
return null;
}
Map<Integer, Integer> hashMap = new HashMap<Integer, Integer>();
for (int data : array) {
hashMap.put(data,
hashMap.containsKey(data) == false ? 1
:hashMap.get(data)+1);
}
return hashMap;
}
public static void main(String[] args) {
int array[] = {2, 5, 6, 2, 4, 3, 5, 4, 2, 5, 2, 4, 2, 6, 3, 5, 4, 2,
3, 5, 6, 5, 2, 4, 2, 5, 2, 3, 5, 2, 3, 5, 2, 3, 5, 2, 3, 5, 2,
7, 8, 8, 7, 8, 7, 9, 0};
MapSort mp = new MapSort();
Map<Integer, Integer> map = mp.getCountSortMap(array);
Iterator it = map.entrySet().iterator();
while(it.hasNext()){
Map.Entry<Integer, Integer> me = (Map.Entry)it.next();
int key = me.getKey();
int count = me.getValue();
System.out.println("data-> " + key + "; count-> " +count);
}
}
}
这个思路不错,可惜没有按出现次数排序,于是乎:
5.2 TreeMap
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
/**
* 题目:数组中有重复数据,求出每个数据出现的次数并按照次数的由大到小排列出来
* 红黑树 按键值自然升序排列
*/
public class TreeMapSort {
public void countSortTreeMap(int[] array){
if(null == array){
return;
}
Map<Integer, Integer> hashMap = new HashMap<Integer, Integer>();
for (int data : array) {
hashMap.put(data,
hashMap.containsKey(data) == false ? 1
:hashMap.get(data)+1);
}
//为了排序再构造一棵红黑树 key-出现次数 value-','连接的数值字符串
TreeMap<Integer, String> treeMap = new TreeMap<Integer, String>();
for(int i:hashMap.keySet()){
int count = hashMap.get(i); //将出现次数作为Map的key值
treeMap.put(count, treeMap.containsKey(count) ? treeMap.get(count) + "," +i : i+"");
}
//按key值降序后,输出
for(int key:treeMap.descendingKeySet()){
System.out.println(String.format("数字%s出现了%d次", treeMap.get(key), key));
}
}
public static void main(String[] args) {
int array[] = {2, 5, 6, 2, 4, 3, 5, 4, 2, 5, 2, 4, 2, 6, 3, 5, 4, 2,
3, 5, 6, 5, 2, 4, 2, 5, 2, 3, 5, 2, 3, 5, 2, 3, 5, 2, 3, 5, 2,
7, 8, 8, 7, 8, 7, 9, 0};
TreeMapSort ts = new TreeMapSort();
System.out.println("Go----------------->");
ts.countSortTreeMap(array);
System.out.println("Game over!!");
}
}
呵呵,这篇文章关于数据结构的信息量还是很大的。
感谢CCTV,下次再见。
【补充内容】=====
0 概述
Collection
├List
│├LinkedList
│├ArrayList
│└Vector
│ └Stack
└Set
Map
├Hashtable
├HashMap
└WeakHashMap
Collection接口
Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements)。
Set接口
Set是一种不包含重复的元素的Collection
Map接口
请注意,Map没有继承Collection接口,Map提供key到value的映射。一个Map中不能包含相同的key,每个key只能映射一个value。
1 linkedlist list vector
一、同步性
ArrayList,LinkedList是不同步的,而Vestor是的。
二、数据增长
从内部实现机制来讲ArrayList和Vector都是使用Objec的数组形式来存储的。
如果元素的数目超出了内部数组目前的长度它们都需要扩展内部数组的长度,
Vector缺省情况下自动增长原来一倍的数组长度,ArrayList是原来的50%
三、检索、插入、删除对象的效率
ArrayList和Vector中,从指定的位置(用index)检索一个对象,或在集合的末尾插入、删除一个对象的时间是一样的,可表示为O(1)。
但是,如果在集合的其他位置增加或移除元素那么花费的时间会呈线形增长:O(n-i),其中n代表集合中元素的个数,i代表元素增加或移除元素的索引位置。
LinkedList中,在插入、删除集合中任何位置的元素所花费的时间都是一样的—O(1),但它在索引一个元素的时候比较慢,为O(i),其中i是索引的位置。
所以,如果只是查找特定位置的元素或只在集合的末端增加、移除元素,那么使用Vector或ArrayList都可以。
如果是对其它指定位置的插入、删除操作,最好选择LinkedList
如果涉及到堆栈,队列等操作,应该考虑用List,对于需要快速插入,删除元素,应该使用LinkedList
LinkedList method:
addFirst(E e)
addLast(E e)
getFirst()
getLast()
2 对象map封装结构git
MapList结构
//返回包含Map<名称, 值>对象的结果List
public List getQueryMapList(String sql, Connection conn)
throws Exception
{
List list = new ArrayList();
Statement st = conn.createStatement();
try
{
ResultSet rs = st.executeQuery(sql);
try
{
ResultSetMetaData meta = rs.getMetaData();
for (int idx = 0; rs.next(); ++idx)
{
Map m = new HashMap();
for (int i = 1; i <= meta.getColumnCount(); ++i)
{
m.put(meta.getColumnName(i).toUpperCase(), rs.getObject(i));
}
list.add(m);
}
}
finally
{
rs.close();
}
}
finally
{
st.close();
}
return list;
}
调用举例
for (int i = 0; i < resuList.size(); i++) {
Map map = (Map) resuList.get(i);
String columnName = map.get("COLUMNNAME").toString().trim(); //columnName:列名称
... ...
}