日期: 2021 年 9 月 8 日

数据结构和算法 (三)数据结构基础之树、二叉树

数据结构和算法 (三)数据结构基础之树、二叉树

Java面试宝典之二叉树的实现

我们接着上一篇数据结构继续讲解。本章系数据结构之树与二叉树,从这章开始,我们就要介绍非线性结构了,这些内容理解起来比线性表稍难一些,我尽量写的通俗一些,如果读的过程中有任何问题,请按上述方式联系我!

一、树

树 形结构是一类重要的非线性结构。树形结构是结点之间有分支,并具有层次关系的结构。它非常类似于自然界中的树。树结构在客观世界中是大量存在的,例如家 谱、行政组织机构都可用树形象地表示。树在计算机领域中也有着广泛的应用,例如在编译程序中,用树来表示源程序的语法结构;在数据库系统中,可用树来组织 信息;在分析算法的行为时,可用树来描述其执行过程。本章重点讨论二叉树的存储表示及其各种运算,并研究一般树和森林与二叉树的转换关系,*后介绍树的应 用实例。

二、二叉树

二叉树(BinaryTree)是n(n≥0)个结点的有限集,它或者是空集(n=0),或者由一个根结点及两棵互不相交的、分别称作这个根的左子树和右子树的二叉树组成。关于更多概念,请大家自己上网查询,我们这里将用代码实现常见的算法。更多的概念,请访问:http://student.zjzk.cn/course_ware/data_structure/web/SHU/shu6.2.3.1.htm 。

 

面试中常考到树的前序,中序,后序和层序遍历,这篇博文就带你深度剖析一下二叉树的各类遍历算法的实现

 

二叉树的遍历主要有四种,前序、中序、后序和层序

 

遍历的实现方式主要是:递归和非递归

 

递归遍历的实现非常容易,非递归的实现需要用到栈,难度系数要高一点。

 

一、二叉树节点的定义

 

二叉树的每个节点由节点值、左子树和右子树组成。

 

1
2
3
4
5
6
7
class TreeNode{
public:
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
}

 

二、二叉树的遍历方式

 

前序遍历:先访问根节点,再访问左子树,*后访问右子树

 

中序遍历:先访问左子树,再访问根节点,*后访问右子树

 

后序遍历:先访问左子树,再访问右子树,*后访问根节点

 

层序遍历:每一层从左到右访问每一个节点。

 

举例说明:(以下面的二叉树来说明这四种遍历)

 

%title插图%num

 

前序遍历:ABDFGHIEC
中序遍历:FDHGIBEAC
后序遍历:FHIGDEBCA
层序遍历:ABCDEFGHI

 

1、二叉树的建立

首先,我们采用广义表建立二叉树(关于广义表的概念,请查看百科的介绍:http://baike.baidu.com/view/203611.htm)

我们建立一个字符串类型的广义表作为输入:

String  expression = “A(B(D(,G)),C(E,F))”;与该广义表对应的二叉树为:

%title插图%num

写代码前,我们通过观察二叉树和广义表,先得出一些结论:

  • 每当遇到字母,将要创建节点
  • 每当遇到“(”,表面要创建左孩子节点
  • 每当遇到“,”,表明要创建又孩子节点
  • 每当遇到“)”,表明要返回上一层节点
  • 广义表中“(”的数量正好是二叉树的层数

根据这些结论,我们基本就可以开始写代码了。首先建议一个节点类(这也属于一种自定义的数据结构)。

 

  1. package com.xtfggef.algo.tree;  
  2. public class Node {  
  3.     private char data;  
  4.     private Node lchild;  
  5.     private Node rchild;  
  6.     public Node(){  
  7.     }
  8.     public char getData() {  
  9.         return data;  
  10.     }
  11.     public void setData(char data) {  
  12.         this.data = data;  
  13.     }
  14.     public Node getRchild() {  
  15.         return rchild;  
  16.     }
  17.     public void setRchild(Node rchild) {  
  18.         this.rchild = rchild;  
  19.     }
  20.     public Node getLchild() {  
  21.         return lchild;  
  22.     }
  23.     public void setLchild(Node lchild) {  
  24.         this.lchild = lchild;  
  25.     }
  26.     public Node(char ch, Node rchild, Node lchild) {  
  27.         this.data = ch;  
  28.         this.rchild = rchild;  
  29.         this.lchild = lchild;  
  30.     }
  31.     public String toString() {  
  32.         return “” + getData();  
  33.     }
  34. }

根据广义表创建二叉树的代码如下: 

  1. public Node createTree(String exp) {  
  2.         Node[] nodes = new Node[3];  
  3.         Node b, p = null;  
  4.         int top = –1, k = 0, j = 0;  
  5.         char[] exps = exp.toCharArray();  
  6.         char data = exps[j];  
  7.         b = null;  
  8.         while (j < exps.length – 1) {  
  9.             switch (data) {  
  10.             case ‘(‘:  
  11.                 top++;
  12.                 nodes[top] = p;
  13.                 k = 1;  
  14.                 break;  
  15.             case ‘)’:  
  16.                 top–;
  17.                 break;  
  18.             case ‘,’:  
  19.                 k = 2;  
  20.                 break;  
  21.             default:  
  22.                 p = new Node(data, null, null);  
  23.                 if (b == null) {  
  24.                     b = p;
  25.                 } else {  
  26.                     switch (k) {  
  27.                     case 1:  
  28.                         nodes[top].setLchild(p);
  29.                         break;  
  30.                     case 2:  
  31.                         nodes[top].setRchild(p);
  32.                         break;  
  33.                     }
  34.                 }
  35.             }
  36.             j++;
  37.             data = exps[j];
  38.         }
  39.         return b;  
  40.     }

思路不难,结合上述的理论,自己断点走一遍程序就懂了!2、二叉树的递归遍历

二叉树的遍历有三种:先序、中序、后序,每种又分递归和非递归。递归程序理解起来有一定的难度,但是实现起来比较简单。对于上述二叉树,其:

a 先序遍历

A B D G C E F

b 中序遍历

D G B A E C F

c 后序遍历

G D B E F C A

先、中、后序递归遍历如下:

  1. /** 
  2.      * pre order recursive 
  3.      *  
  4.      * @param node 
  5.      */  
  6.     public void PreOrder(Node node) {  
  7.         if (node == null) {  
  8.             return;  
  9.         } else {  
  10.             System.out.print(node.getData() + ” “);  
  11.             PreOrder(node.getLchild());
  12.             PreOrder(node.getRchild());
  13.         }
  14.     }
  15.     /** 
  16.      * in order recursive 
  17.      *  
  18.      * @param node 
  19.      */  
  20.     public void InOrder(Node node) {  
  21.         if (node == null) {  
  22.             return;  
  23.         } else {  
  24.             InOrder(node.getLchild());
  25.             System.out.print(node.getData() + ” “);  
  26.             InOrder(node.getRchild());
  27.         }
  28.     }
  29.     /** 
  30.      * post order recursive 
  31.      *  
  32.      * @param node 
  33.      */  
  34.     public void PostOrder(Node node) {  
  35.         if (node == null) {  
  36.             return;  
  37.         } else {  
  38.             PostOrder(node.getLchild());
  39.             PostOrder(node.getRchild());
  40.             System.out.print(node.getData() + ” “);  
  41.         }
  42.     }

二叉树的递归遍历实现起来很简单,关键是非递归遍历有些难度,请看下面的代码:3、二叉树的非递归遍历

先序非递归遍历:

 

复制代码
 public void myPreOrderNoRecursive(Node node) {
        Node[] nodes = new Node[CAPACITY];  //创建栈
        int top = -1;                   //栈顶下标
        Node temp;                    //临时变量
        if (node == null) {
            return;
        }
        temp = node;      
        top++;        //将根节点压入栈中
        nodes[top] = temp;
       //循环遍历栈中元素,一个个取出处理,然后再把右节点和左节点压入栈
        while (top > -1) {
            temp = nodes[top];
            top--;
            mPreOrder2 += temp.getData();
            if (temp.getRchild() != null) {
                top++;
                nodes[top] = temp.getRchild();
            }

            if (temp.getLchild() != null) {
                top++;
                nodes[top] = temp.getLchild();
            }
        }

    }






public static void preOrderStack(Node node){
    if(node == null){
        return;
    }
    Stack<Node> stack = new Stack();
    stack.push(node);
    while(!stack.empty()){

        Node temp = stack.pop();
         Log.e("VDSADGSA", temp.getData()+"");

        if(temp.getRchild() != null){
            stack.push(temp.getRchild());
        }

        if(temp.getLchild() != null){
            stack.push(temp.getLchild());
        }
    }
}

复制代码

 

原 理:利用一个栈,先序遍历即为根先遍历,先将根入栈,然后出栈,凡是出栈的元素都打印值,入栈之前top++,出栈之后top–,利用栈后进先出的原 理,右节点先于左节点进栈,根出栈后,开始处理左子树,然后是右子树,读者朋友们可以自己走一遍程序看看,也不算难理解!中序非递归遍历:

 

复制代码
 public void myInOrderNoRecursive(Node node) {
        Node[] nodes = new Node[CAPACITY];  //栈中只保存处理过左节点的根节点
        int top = -1;
        Node temp;
        if (node == null) {
            return;
        }
        temp = node;
        //循环处理 原始根节点和栈中已经处理过左节点的根节点
        while (temp != null || top > -1) {
            //处理原始根节点,将原始根节点的左节点取出并处理,再将处理过左节点的原始根节点压入栈中
            while (temp != null) {
                top++;
                nodes[top] = temp;
                temp = temp.getLchild();
            }

            //从栈中取出处理过左节点的根节点,处理根节点,然后将右节点当作原始根节点进行处理
            if (top > -1) {
                temp = nodes[top];
                mInOrder2 += temp.getData();
                top--;
                temp = temp.getRchild();
            }
        }

    }







public static void inOrderStack(Node node){
    if(node == null){
        return;
    }
    Stack<Node> stack = new Stack<>();
    while(node != null || !stack.empty()){

        while(node != null){
            stack.push(node);
            node = node.getLchild();
        }

        if(!stack.empty()){
            Node pop = stack.pop();
            Log.e("VDSADGSA", pop.getData()+"");
            node = pop.getRchild();
        }

    }

}

复制代码

 

原理省略。后续非递归遍历:

 

复制代码
  public void myPostOrderNoRecursive(Node node) {
        Node[] nodes = new Node[CAPACITY];
        int top = -1;
        Node temp1, temp2 = null;
        if (node == null) {
            return;
        }
        temp1 = node;
        while (temp1 != null || top > -1) {
            while (temp1 != null) {
                top++;
                nodes[top] = temp1;
                temp1 = temp1.getLchild();
            }
            //从栈中取出处理过左节点的根节点,处理根节点,然后将右节点当作原始根节点进行处理
            if (top > -1) {
                temp1 = nodes[top];

                //从栈中取出的处理过左节点的根节点,判断其右节点是否处理过,如果右节点为空或者上个循环处理的节点就是其右节点则认为取出的根节点的右节点已经处理
                if (temp1.getRchild() == null || temp1.getRchild() == temp2) {
                    mPostOrder2 += temp1.getData();
                    top--;
                    temp2 = temp1;
                    temp1 = null;
                } else {
//                    如果 右节点没有处理,则将右节点当作原始根节点进行处理
                    temp2 = null;
                    temp1 = temp1.getRchild();
                }

            }
        }
    }






public static void postOrderStack(Node node){
    if(node == null){
        return;
    }
    Stack<Node> stack = new Stack<>();
    Node temp = null;

while (node != null || !stack.empty()){

    while(node != null ){
        stack.push(node);
        node = node.getLchild();
    }


    if(!stack.empty()){
        Node peek = stack.peek();
        if(peek.getRchild() == null || peek.getRchild() == temp){
            Log.e("VDSADGSA", peek.getData()+"");
            stack.pop();
            temp = peek;
            node = null;
        }else {
            temp = null;
            node = peek.getRchild();
        }
    }
}
}





复制代码

 

三、树与二叉树的转换

本人之前总结的:

%title插图%num

 

这部分概念的其他知识,请读者自己上网查看。

数据结构和算法 (二)数据结构基础、线性表、栈和队列、数组和字符串

数据结构和算法 (二)数据结构基础、线性表、栈和队列、数组和字符串

Java面试宝典之数据结构基础 —— 线性表篇

 

一、数据结构概念

用我的理解,数据结构包含数据和结构,通俗一点就是将数据按照一定的结构组合起来,不同的组合方式会有不同的效率,使用不同的场景,如此而已。比 如我们*常用的数组,就是一种数据结构,有独特的承载数据的方式,按顺序排列,其特点就是你可以根据下标快速查找元素,但是因为在数组中插入和删除元素会 有其它元素较大幅度的便宜,所以会带来较多的消耗,所以因为这种特点,使得数组适合:查询比较频繁,增、删比较少的情况,这就是数据结构的概念。数据结构 包括两大类:线性结构和非线性结构,线性结构包括:数组、链表、队列、栈等,非线性结构包括树、图、表等及衍生类结构。本章我们先讲解线性结构,主要从数 组、链表、队列、栈方面进行讨论,非线性数据结构在后面会继续讲解。

 

二、线性表

线 性表是*基本、*简单、也是*常用的一种数据结构。线性表中数据元素之间的关系是一对一的关系,即除了*个和*后一个数据元素之外,其它数据元素都是首 尾相接的。线性表的逻辑结构简单,便于实现和操作。因此,线性表这种数据结构在实际应用中是广泛采用的一种数据结构。其基本操作主要有:

1)MakeEmpty(L) 这是一个将L变为空表的方法
2)Length(L) 返回表L的长度,即表中元素个数
3)Get(L,i) 这是一个函数,函数值为L中位置i处的元素(1≤i≤n)
4)Prev(L,i) 取i的前驱元素
5)Next(L,i) 取i的后继元素
6)Locate(L,x) 这是一个函数,函数值为元素x在L中的位置
7)Insert(L,i,x)在表L的位置i处插入元素x,将原占据位置i的元素及后面的元素都向后推一个位置
8)Delete(L,p) 从表L中删除位置p处的元素
9)IsEmpty(L) 如果表L为空表(长度为0)则返回true,否则返回false
10)Clear(L)清除所有元素
11)Init(L)同*个,初始化线性表为空
12)Traverse(L)遍历输出所有元素
13)Find(L,x)查找并返回元素
14)Update(L,x)修改元素
15)Sort(L)对所有元素重新按给定的条件排序
16) strstr(string1,string2)用于字符数组的求string1中出现string2的首地址

不管采用哪种方式实现线性表,至少都应该具有上述这些基本方法,下面我会将下数据结构的基本实现方式。

 

三、基础数据结构

数 据结构是一种抽象的数据类型(ADT),可以这么说,我们可以采用任意的方式实现某种数据结构,只要符合将要实现的数据结构的特点,数据结构就是一种标 准,我们可以采用不同的方式去实现,*常用的两种就是数组和链表(包括单链表、双向链表等)。数组是非常常见的数据类型,在任何一种语言里都有它的实现, 我们这里采用Java来简单实现一下数组。
数组是一种引用类型的对象,我们可以像下面这样的方式来声明数组:

 

  1. int a[];  
  2. int[] b;  
  3. int []c;  

 

  1. a = new int[10];  

总结起来,声明一个数组有基本的三个因素:类型、名称、下标,Java里,数组在格式上相对灵活,下标和名称可以互换位置,前三种情况我们可以理解为声明一个变量,后一种为其赋值。或者像下面这样,在声明的时候赋值:

  1. int c[] = {2,3,6,10,99};  
  2. int []d = new int[10];  

 

我 稍微解释一下,其实如果只执行:int[] b,只是在栈上创建一个引用变量,并未赋值,只有当执行d = new int[10]才会在堆上真正的分配空间。上述*行为静态初始化,就是说用户指定数组的内容,有系统计算数组的大小,第二行恰恰相反,用户指定数组的大 小,由系统分配初始值,我们打印一下数组的初始值:

 

  1. int []d = new int[10];  
  2. System.out.println(d[2]);  

结果输出0,对于int类型的数组,默认的初始值为0.

但是,*对不可以像下面这样:

  1. int e[10] = new int[10];  

无法通过编译,至于为什么,语法就是这样,这是一种规范,不用去想它。
我们可以通过下标来检索数组。下面我举个简单的例子,来说明下数组的用法。

  1. public static void main(String[] args) {  
  2.     String name[];
  3.     name = new String[5];  
  4.     name[0] = “egg”;  
  5.     name[1] = “erqing”;  
  6.     name[2] = “baby”;  
  7.     for (int i = 0; i < name.length; i++) {  
  8.         System.out.println(name[i]);
  9.     }
  10. }

这是*简单的数组声明、创建、赋值、遍历的例子,下面写个增删的例子。

  1. package com.xtfggef.algo.array;  
  2. public class Array {  
  3.     public static void main(String[] args) {  
  4.         int value[] = new int[10];  
  5.         for (int i = 0; i < 10; i++) {  
  6.             value[i] = i;
  7.         }
  8.         // traverse(value);  
  9.         // insert(value, 666, 5);  
  10.         delete(value, 3);  
  11.         traverse(value);
  12.     }
  13.     public static int[] insert(int[] old, int value, int index) {  
  14.         for (int k = old.length – 1; k > index; k–)  
  15.             old[k] = old[k – 1];  
  16.         old[index] = value;
  17.         return old;  
  18.     }
  19.     public static void traverse(int data[]) {  
  20.         for (int j = 0; j < data.length; j++)  
  21.             System.out.print(data[j] + ” “);  
  22.     }
  23.     public static int[] delete(int[] old, int index) {  
  24.         for (int h = index; h < old.length – 1; h++) {  
  25.             old[h] = old[h + 1];  
  26.         }
  27.         old[old.length – 1] = 0;  
  28.         return old;  
  29.     }
  30. }

简单写一下,主要想说明数组中删除和增加元素的原理:增加元素,需要将index后面的依次向后移动,然后将值插入index位置,删除则将后面的值依次向前移动,较简单。

要记住:数组是表示相同类型的一类数据的集合,下标从0开始,就行了。

数组实现的线下表可以参考ArrayList,在JDK中附有源码,感兴趣的同学可以读读。下面我简单介绍下单链表。

单链表是*简单的链表,有节点之间首尾连接而成,简单示意如下:

%title插图%num

除了头节点,每个节点包含一个数据域一个指针域,除了头、尾节点,每个节点的指针指向下一个节点,下面我们写个例子操作一下单链表。

  1. package com.xtfggef.algo.linkedlist;  
  2. public class LinkedList<T> {  
  3.     /** 
  4.      * class node 
  5.      * @author egg 
  6.      * @param <T> 
  7.      */  
  8.     private static class Node<T> {  
  9.         T data;
  10.         Node<T> next;
  11.         Node(T data, Node<T> next) {
  12.             this.data = data;  
  13.             this.next = next;  
  14.         }
  15.         Node(T data) {
  16.             this(data, null);  
  17.         }
  18.     }
  19.     // data  
  20.     private Node<T> head, tail;  
  21.     public LinkedList() {  
  22.         head = tail = null;  
  23.     }
  24.     /** 
  25.      * judge the list is empty 
  26.      */  
  27.     public boolean isEmpty() {  
  28.         return head == null;  
  29.     }
  30.     /** 
  31.      * add head node 
  32.      */  
  33.     public void addHead(T item) {  
  34.         head = new Node<T>(item);  
  35.         if (tail == null)  
  36.             tail = head;
  37.     }
  38.     /** 
  39.      * add the tail pointer 
  40.      */  
  41.     public void addTail(T item) {  
  42.         if (!isEmpty()) {   
  43.             tail.next = new Node<T>(item);  
  44.             tail = tail.next;
  45.         } else {   
  46.             head = tail = new Node<T>(item);  
  47.         }
  48.     }
  49.     /** 
  50.      * print the list 
  51.      */  
  52.     public void traverse() {  
  53.         if (isEmpty()) {  
  54.             System.out.println(“null”);  
  55.         } else {  
  56.             for (Node<T> p = head; p != null; p = p.next)  
  57.                 System.out.println(p.data);
  58.         }
  59.     }
  60.     /** 
  61.      * insert node from head 
  62.      */  
  63.     public void addFromHead(T item) {  
  64.         Node<T> newNode = new Node<T>(item);  
  65.         newNode.next = head;
  66.         head = newNode;
  67.     }
  68.     /** 
  69.      * insert node from tail 
  70.      */  
  71.     public void addFromTail(T item) {  
  72.         Node<T> newNode = new Node<T>(item);  
  73.         Node<T> p = head;
  74.         while (p.next != null)  
  75.             p = p.next;
  76.         p.next = newNode;
  77.         newNode.next = null;  
  78.     }
  79.     /** 
  80.      * delete node from head 
  81.      */  
  82.     public void removeFromHead() {  
  83.         if (!isEmpty())  
  84.             head = head.next;
  85.         else  
  86.             System.out.println(“The list have been emptied!”);  
  87.     }
  88.     /** 
  89.      * delete frem tail, lower effect 
  90.      */  
  91.     public void removeFromTail() {  
  92.         Node<T> prev = null, curr = head;  
  93.         while (curr.next != null) {  
  94.             prev = curr;
  95.             curr = curr.next;
  96.             if (curr.next == null)  
  97.                 prev.next = null;  
  98.         }
  99.     }
  100.     /** 
  101.      * insert a new node 
  102.      * @param appointedItem 
  103.      * @param item 
  104.      * @return 
  105.      */  
  106.     public boolean insert(T appointedItem, T item) {  
  107.         Node<T> prev = head, curr = head.next, newNode;
  108.         newNode = new Node<T>(item);  
  109.         if (!isEmpty()) {  
  110.             while ((curr != null) && (!appointedItem.equals(curr.data))) {  
  111.                 prev = curr;
  112.                 curr = curr.next;
  113.             }
  114.             newNode.next = curr;
  115.             prev.next = newNode;
  116.             return true;  
  117.         }
  118.         return false;   
  119.     }
  120.     public void remove(T item) {  
  121.         Node<T> curr = head, prev = null;  
  122.         boolean found = false;  
  123.         while (curr != null && !found) {  
  124.             if (item.equals(curr.data)) {  
  125.                 if (prev == null)  
  126.                     removeFromHead();
  127.                 else  
  128.                     prev.next = curr.next;
  129.                 found = true;  
  130.             } else {  
  131.                 prev = curr;
  132.                 curr = curr.next;
  133.             }
  134.         }
  135.     }
  136.     public int indexOf(T item) {  
  137.         int index = 0;  
  138.         Node<T> p;
  139.         for (p = head; p != null; p = p.next) {  
  140.             if (item.equals(p.data))  
  141.                 return index;  
  142.             index++;
  143.         }
  144.         return –1;  
  145.     }
  146.     /** 
  147.      * judge the list contains one data 
  148.      */  
  149.     public boolean contains(T item) {  
  150.         return indexOf(item) != –1;  
  151.     }
  152. }

单链表*好玩儿的也就是增加和删除节点,下面的两个图分别是用图来表示单链表增、删节点示意,看着图学习,理解起来更加容易!

%title插图%num

接下来的队列和栈,我们分别用不同的结构来实现,队列用数组,栈用单列表,读者朋友对此感兴趣,可以分别再用不同的方法实现。

四、队列
队列是一个常用的数据结构,是一种先进先出(First In First Out, FIFO)的结构,也就是说只能在表头进行删除,在表尾进行添加,下面我们实现一个简单的队列。

 

  1. package com.xtfggef.algo.queue;  
  2. import java.util.Arrays;  
  3. public class Queue<T> {  
  4.     private int DEFAULT_SIZE = 10;  
  5.     private int capacity;  
  6.     private Object[] elementData;  
  7.     private int front = 0;  
  8.     private int rear = 0;  
  9.     public Queue()  
  10.     {
  11.         capacity = DEFAULT_SIZE;
  12.         elementData = new Object[capacity];  
  13.     }
  14.     public Queue(T element)  
  15.     {
  16.         this();  
  17.         elementData[0] = element;  
  18.         rear++;
  19.     }
  20.     public Queue(T element , int initSize)  
  21.     {
  22.         this.capacity = initSize;  
  23.         elementData = new Object[capacity];  
  24.         elementData[0] = element;  
  25.         rear++;
  26.     }
  27.     public int size()  
  28.     {
  29.         return rear – front;  
  30.     }
  31.     public void add(T element)  
  32.     {
  33.         if (rear > capacity – 1)  
  34.         {
  35.             throw new IndexOutOfBoundsException(“the queue is full!”);  
  36.         }
  37.         elementData[rear++] = element;
  38.     }
  39.     public T remove()  
  40.     {
  41.         if (empty())  
  42.         {
  43.             throw new IndexOutOfBoundsException(“queue is empty”);  
  44.         }
  45.         @SuppressWarnings(“unchecked”)  
  46.         T oldValue = (T)elementData[front];
  47.         elementData[front++] = null;   
  48.         return oldValue;  
  49.     }
  50.     @SuppressWarnings(“unchecked”)  
  51.     public T element()    
  52.     {
  53.         if (empty())    
  54.         {
  55.             throw new IndexOutOfBoundsException(“queue is empty”);    
  56.         }
  57.         return (T)elementData[front];    
  58.     }
  59.     public boolean empty()  
  60.     {
  61.         return rear == front;  
  62.     }
  63.     public void clear()  
  64.     {
  65.         Arrays.fill(elementData , null);  
  66.         front = 0;  
  67.         rear = 0;  
  68.     }
  69.     public String toString()  
  70.     {
  71.         if (empty())  
  72.         {
  73.             return “[]”;  
  74.         }
  75.         else  
  76.         {
  77.             StringBuilder sb = new StringBuilder(“[“);  
  78.             for (int i = front  ; i < rear ; i++ )  
  79.             {
  80.                 sb.append(elementData[i].toString() + “, “);  
  81.             }
  82.             int len = sb.length();  
  83.             return sb.delete(len – 2 , len).append(“]”).toString();  
  84.         }
  85.     }
  86.     public static void main(String[] args){  
  87.         Queue<String> queue = new Queue<String>(“ABC”, 20);  
  88.         queue.add(“DEF”);  
  89.         queue.add(“egg”);  
  90.         System.out.println(queue.empty());
  91.         System.out.println(queue.size());
  92.         System.out.println(queue.element());
  93.         queue.clear();
  94.         System.out.println(queue.empty());
  95.         System.out.println(queue.size());
  96.     }
  97. }

队列只能在表头进行删除,在表尾进行增加,这种结构的特点,适用于排队系统。

五、栈

栈是一种后进先出(Last In First Out,LIFO)的数据结构,我们采用单链表实现一个栈。

  1. package com.xtfggef.algo.stack;  
  2. import com.xtfggef.algo.linkedlist.LinkedList;  
  3. public class Stack<T> {  
  4.     static class Node<T> {  
  5.         T data;
  6.         Node<T> next;
  7.         Node(T data, Node<T> next) {
  8.             this.data = data;  
  9.             this.next = next;  
  10.         }
  11.         Node(T data) {
  12.             this(data, null);  
  13.         }
  14.     }
  15.     @SuppressWarnings(“rawtypes”)  
  16.     static LinkedList list = new LinkedList();  
  17.     @SuppressWarnings(“unchecked”)  
  18.     public T push(T item) {  
  19.         list.addFromHead(item);
  20.         return item;  
  21.     }
  22.     public void pop() {  
  23.         list.removeFromHead();
  24.     }
  25.     public boolean empty() {  
  26.         return list.isEmpty();  
  27.     }
  28.     public int search(T t) {  
  29.         return list.indexOf(t);  
  30.     }
  31.     public static void main(String[] args) {  
  32.         Stack<String> stack = new Stack<String>();  
  33.         System.out.println(stack.empty());
  34.         stack.push(“abc”);  
  35.         stack.push(“def”);  
  36.         stack.push(“egg”);  
  37.         stack.pop();
  38.         System.out.println(stack.search(“def”));  
  39.     }
  40. }

本章的内容都是很基础的,重在让读者朋友们理解数据结构的概念,下章开始,我们会介绍树、二叉树等Java中的实现,敬请读者朋友们持续关注!

数据结构和算法 (一)常见的几种排序算法-插入、选择、冒泡、快排、堆排等

数据结构和算法 (一)常见的几种排序算法-插入、选择、冒泡、快排、堆排等

Java面试宝典系列之基础排序算法

本文就是介绍一些常见的排序算法。排序是一个非常常见的应用场景,很多时候,我们需要根据自己需要排序的数据类型,来自定义排序算法,但是,在这里,我们只介绍这些基础排序算法,包括:插入排序、选择排序、冒泡排序、快速排序(重点)、堆排序、归并排序等等。看下图:

%title插图%num

给定数组:int data[] = {9,2,7,19,100,97,63,208,55,78}

一、直接插入排序(内部排序、O(n2)、稳定)

原理:从待排序的数中选出一个来,插入到前面的合适位置。

  1. package com.xtfggef.algo.sort;  
  2. public class InsertSort {  
  3.     static int data[] = { 9, 2, 7, 19, 100, 97, 63, 208, 55, 78 };  
  4.   
  5. 
    
    复制代码
     public static void insertSort() {  
    
                for (int i = 1; i < unsorted.Length; i++)
                {
                    if (unsorted[i - 1] > unsorted[i])
                    {
                        int temp = unsorted[i];
                        int j = i;
                        while (j > 0 && unsorted[j - 1] > temp)
                        {
                            unsorted[j] = unsorted[j - 1];
                            j--;
                        }
                        unsorted[j] = temp;
                    }
                }
    }
         
    
    
    
     public static void insertSort() {  
       for (int i = 1; i < data.length; i++) {
      if (data[i] >= data[i - 1]) {
        continue;
      }
      int temp = data[i];
      for (int j = i - 1; j >= 0; j--) {
        if (temp < data[j]) {
          data[j + 1] = data[j];
         if(j==0){
           data[j ] = temp;
          }
        
        } else {
          data[j + 1] = temp;
          break;
        }
      }
    }
    
    
    
    }
         
    
    
    复制代码
    
    

     

    
    
  6.     public static void main(String[] args) {  
  7.         print();
  8.         System.out.println();
  9.         insertSort();
  10.         System.out.println();
  11.         print();
  12.     }
  13.     static void print() {  
  14.         for (int i = 0; i < data.length; i++) {  
  15.             System.out.print(data[i] + ” “);  
  16.         }
  17.     }
  18. }

我简单的讲解一下过程:思路上从待排序的数据中选出一个,插入到前面合适的位置,耗时点在插入方面,合适的位置意味着我们需要进行比较找出哪是合适的位置,举个例子:对于9,2,7,19,100,97,63,208,55,78 这组数,*个数9前面没有,不做操作,当*个数完后,剩下的数就是待排序的数,我们将要从除去9开始的书中选出一个插入到前面合适的位置,拿到2后, 放在tmp上,进行注释中的2处的代码,2处的代码就是通过循环找出这个合适的位置,发现比tmp大的数,立即将该数向后移动一位(这样做的目的是:前面 需要空出一位来进行插入),*后通过注释3处的代码将数插入。

本排序适合:基本有序的数据

二、选择排序(O(n2)、不稳定)
与直接插入排序正好相反,选择排序是从待排序的数中选出*小的放在已经排好的后面,这个算法选数耗时。

  1. package com.xtfggef.algo.sort;  
  2. public class SelectSort {  
  3.     static int data[] = { 9, 2, 7, 19, 100, 97, 63, 208, 55, 78 };  
  4.     public static void selectSort() {  
  5.         int i, j, k, tmp = 0;  
  6.         for (i = 0; i < data.length – 1; i++) {  
  7.             k = i;
  8.             for (j = i + 1; j < data.length; j++)  
  9.                 if (data[j] < data[k])  
  10.                     k = j;
  11.             if (k != i) {  
  12.                 tmp = data[i];
  13.                 data[i] = data[k];
  14.                 data[k] = tmp;
  15.             }
  16.         }
  17.     }
  18.     public static void main(String[] args) {  
  19.         print();
  20.         System.out.println();
  21.         selectSort();
  22.         System.out.println();
  23.         print();
  24.     }
  25.     static void print() {  
  26.         for (int i = 0; i < data.length; i++) {  
  27.             System.out.print(data[i] + ” “);  
  28.         }
  29.     }
  30. }

通过循环,找出*小的数的下标,赋值于k,即k永远保持待排序数据中*小的数的下标,*后和当前位置i互换数据即可。

三、快速排序(O(nlogn)、不稳定)

快速排序简称快排,是一种比较快的排序,适合基本无序的数据,为什么这么说呢?下面我说下快排的思路:

设置两个指针:i和j,分别指向*个和*后一个,i像后移动,j向前移动,选*个数为标准(一般这样做,当然快排的关键就是这个“标准”的选取),从后面开始,找到*个比标准小的数,互换位置,然后再从前面,找到*个比标准大的数,互换位置,*趟的结果就是标准左边的都小于标准,右边的都大于标准(但不一定有序),分成两拨后,继续递归的使用上述方法,*终有序!代码如下:

 

  1. package com.xtfggef.algo.sort;  
  2. public class QuickSortTest {  
  3.     static class QuickSort {  
  4.         public int data[];  
  5.         private int partition(int array[], int low, int high) {  
  6.             int key = array[low];  
  7.             while (low < high) {  
  8.                 while (low < high && array[high] >= key)  
  9.                     high–;
  10.                 array[low] = array[high];
  11.                 while (low < high && array[low] <= key)  
  12.                     low++;
  13.                 array[high] = array[low];
  14.             }
  15.             array[low] = key;
  16.             return low;  
  17.         }
  18.         public int[] sort(int low, int high) {  
  19.             if (low < high) {  
  20.                 int result = partition(data, low, high);  
  21.                 sort(low, result – 1);  
  22.                 sort(result + 1, high);  
  23.             }
  24.             return data;  
  25.         }
  26.     }
  27.     static void print(int data[]) {  
  28.         for (int i = 0; i < data.length; i++) {  
  29.             System.out.print(data[i] + ” “);  
  30.         }
  31.     }
  32.     public static void main(String[] args) {  
  33.         int data[] = { 20, 3, 10, 9, 186, 99, 200, 96, 3000 };  
  34.         print(data);
  35.         System.out.println();
  36.         QuickSort qs = new QuickSort();  
  37.         qs.data = data;
  38.         qs.sort(0, data.length – 1);  
  39.         print(data);
  40.     }
  41.  

    https://tool.lu/

    class Untitled {
    public static void main(String[] args) {

    int data[] = { 9, 2, 7, 19, 100, 97, 63, 208, 55, 78 };

    kuaisu1(data,0,data.length-1);
    for (int k = 0; k < data.length; k++) {
    System.out.println(data[k]);
    }

    }

    public static void kuaisu1(int[] args,int left,int right) {

    if(left<right){
    int center=kuaisu2(args,left,right);
    kuaisu1(args,left,center-1);
    kuaisu1(args,center+1,right);
    }

    }

    public static int kuaisu2(int[] args,int left,int right) {
    int temp=args[left];

    while(left<right){
    while(left<right&&args[right]>=temp){
    right–;
    }

    args[left]=args[right];
    while(left<right&&args[left]<=temp){
    left++;
    }

    args[right]=args[left];

    }
    args[left]=temp;
    return left;

    }
    }

%title插图%num

看看上面的图,基本就明白了。

 

四、冒泡排序(稳定、基本有序可达O(n),*坏情况为O(n2))

冒泡排序是一种很简单,不论是理解还是时间起来都比较容易的一种排序算法,思路简单:小的数一点一点向前起泡,*终有序。

 

  1. package com.xtfggef.algo.sort;  
  2. public class BubbleSort {  
  3.     static int data[] = { 9, 2, 7, 19, 100, 97, 63, 208, 55, 78 };  
  4.     public static void bubbleSort() {  
  5.         int i, j, tmp = 0;  
  6.         for (i = 0; i < data.length – 1; i++) {  
  7.             for (j = data.length – 1; j > i; j–) {  
  8.                 if (data[j] < data[j – 1]) {  
  9.                     tmp = data[j];
  10.                     data[j] = data[j – 1];  
  11.                     data[j – 1] = tmp;  
  12.                 }
  13.             }
  14.         }
  15.     }
  16.     public static void main(String[] args) {  
  17.         print();
  18.         System.out.println();
  19.         bubbleSort();
  20.         System.out.println();
  21.         print();
  22.     }
  23.     static void print() {  
  24.         for (int i = 0; i < data.length; i++) {  
  25.             System.out.print(data[i] + ” “);  
  26.         }
  27.     }
  28. public static void main(String[] args) {

    int data[] = { 9, 2, 7, 19, 100, 97, 63, 208, 55, 78 };

    for (int i = 0; i < data.length – 1; i++) {
    for (int j =0; j < data.length -i-1; j++) {
    if (data[j] > data[j + 1]) {
    int tmp = data[j];
    data[j] = data[j + 1];
    data[j + 1] = tmp;
    }
    }
    }
    for (int k = 0; k < data.length; k++) {
    System.out.println(data[k]);
    }

    }

 

五、堆排序

我们这里不详细介绍概念,堆的话,大家只要记得堆是一个完全二叉树(什么是完全二叉树,请不懂的读者去查资料),堆排序分为两种堆,大顶堆和小顶堆,大顶堆的意思就是堆顶元素是整个堆中*大的,小顶堆的意思就是堆顶元素是整个堆中*小的,满足:任何一非叶节点的关键字不大于或者不小于其左右孩子节点的关键字。堆排序是一个相对难理解的过程,下面我会较为清楚、详细的讲解一下堆排序。堆排序分为三个过程:

建堆:从一个数组顺序读取元素,建立一个堆(完全二叉树)

初始化:将堆进行调整,使得堆顶为*大(*大堆)或者*小(*小堆)的元素

维护:将堆顶元素出堆后,需要将堆的*后一个节点补充到堆顶,因为这样破坏了堆的秩序,所以需要进行维护。下面我们图示一下:

一般情况,建堆和初始化同步进行,

%title插图%num

%title插图%num

*后为如下所示,即为建堆、初始化成功。

%title插图%num

我们可以观察下这个*大堆,看出堆顶是整个堆中*大的元素,而且除叶子节点外每个节点都大于其子节点。下面的过程就是当我们输出堆顶元素后,对堆进行维护。

%title插图%num

过程是这样:将堆顶元素出堆后,用*后一个元素补充堆顶元素,这样破坏了之前的秩序,需要重新维护堆,在堆顶元素的左右节点中选出较小的和堆顶互换,然后一直递归下去,所以每次出一个元素,需要一次维护,堆排序适合解决topK问题,能将复杂度降到nlogK。下面是代码:

  1. package com.xtfggef.algo.sort;  
  2. public class HeapSort {  
  3.     private static int[] sort = new int[] { 1, 0, 10, 20, 3, 5, 6, 4, 9, 8, 12,  
  4.             17, 34, 11 };  
  5.     public static void main(String[] args) {  
  6.         buildMaxHeapify(sort);
  7.         heapSort(sort);
  8.         print(sort);
  9.     }
  10.     private static void buildMaxHeapify(int[] data) {  
  11.         // 没有子节点的才需要创建*大堆,从*后一个的父节点开始  
  12.         int startIndex = getParentIndex(data.length – 1);  
  13.         // 从尾端开始创建*大堆,每次都是正确的堆  
  14.         for (int i = startIndex; i >= 0; i–) {  
  15.             maxHeapify(data, data.length, i);
  16.         }
  17.     }
  18.     /** 
  19.      * 创建*大堆 
  20.      *  
  21.      * @param data 
  22.      * @param heapSize 
  23.      *            需要创建*大堆的大小,一般在sort的时候用到,因为*多值放在末尾,末尾就不再归入*大堆了 
  24.      * @param index 
  25.      *            当前需要创建*大堆的位置 
  26.      */  
  27.     private static void maxHeapify(int[] data, int heapSize, int index) {  
  28.         // 当前点与左右子节点比较  
  29.         int left = getChildLeftIndex(index);  
  30.         int right = getChildRightIndex(index);  
  31.         int largest = index;  
  32.         if (left < heapSize && data[index] < data[left]) {  
  33.             largest = left;
  34.         }
  35.         if (right < heapSize && data[largest] < data[right]) {  
  36.             largest = right;
  37.         }
  38.         // 得到*大值后可能需要交换,如果交换了,其子节点可能就不是*大堆了,需要重新调整  
  39.         if (largest != index) {  
  40.             int temp = data[index];  
  41.             data[index] = data[largest];
  42.             data[largest] = temp;
  43.             maxHeapify(data, heapSize, largest);
  44.         }
  45.     }
  46.     /** 
  47.      * 排序,*大值放在末尾,data虽然是*大堆,在排序后就成了递增的 
  48.      *  
  49.      * @param data 
  50.      */  
  51.     private static void heapSort(int[] data) {  
  52.         // 末尾与头交换,交换后调整*大堆  
  53.         for (int i = data.length – 1; i > 0; i–) {  
  54.             int temp = data[0];  
  55.             data[0] = data[i];  
  56.             data[i] = temp;
  57.             maxHeapify(data, i, 0);  
  58.         }
  59.     }
  60.     /** 
  61.      * 父节点位置 
  62.      *  
  63.      * @param current 
  64.      * @return 
  65.      */  
  66.     private static int getParentIndex(int current) {  
  67.         return (current – 1) >> 1;  
  68.     }
  69.     /** 
  70.      * 左子节点position 注意括号,加法优先级更高 
  71.      *  
  72.      * @param current 
  73.      * @return 
  74.      */  
  75.     private static int getChildLeftIndex(int current) {  
  76.         return (current << 1) + 1;  
  77.     }
  78.     /** 
  79.      * 右子节点position 
  80.      *  
  81.      * @param current 
  82.      * @return 
  83.      */  
  84.     private static int getChildRightIndex(int current) {  
  85.         return (current << 1) + 2;  
  86.     }
  87.     private static void print(int[] data) {  
  88.         int pre = –2;  
  89.         for (int i = 0; i < data.length; i++) {  
  90.             if (pre < (int) getLog(i + 1)) {  
  91.                 pre = (int) getLog(i + 1);  
  92.                 System.out.println();
  93.             }
  94.             System.out.print(data[i] + ” |”);  
  95.         }
  96.     }
  97.     /** 
  98.      * 以2为底的对数 
  99.      *  
  100.      * @param param 
  101.      * @return 
  102.      */  
  103.     private static double getLog(double param) {  
  104.         return Math.log(param) / Math.log(2);  
  105.     }
  106. }

慢慢理解一下,还是容易明白的!

六、归并排序

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
首先考虑下如何将将二个有序数列合并。这个非常简单,只要从比较二个数列的*个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。

 

  1. package com.xtfggef.algo.sort;  
  2. public class SortTest {  
  3.     // 将有序数组a[]和b[]合并到c[]中  
  4.     static void MemeryArray(int a[], int n, int b[], int m, int c[]) {  
  5.         int i, j, k;  
  6.         i = j = k = 0;  
  7.         while (i < n && j < m) {  
  8.             if (a[i] < b[j])  
  9.                 c[k++] = a[i++];
  10.             else  
  11.                 c[k++] = b[j++];
  12.         }
  13.         while (i < n)  
  14.             c[k++] = a[i++];
  15.         while (j < m)  
  16.             c[k++] = b[j++];
  17.     }
  18.     public static void main(String[] args) {  
  19.         int a[] = { 2, 7, 8, 10, 299 };  
  20.         int b[] = { 5, 9, 14, 20, 66, 88, 92 };  
  21.         int c[] = new int[a.length + b.length];  
  22.         MemeryArray(a, 5, b, 7, c);  
  23.         print(c);
  24.     }
  25.     private static void print(int[] c) {  
  26.         for (int i = 0; i < c.length; i++) {  
  27.             System.out.print(c[i] + ” “);  
  28.         }
  29.     }
  30. }

可以看出合并有序数列的效率是比较高的,可以达到O(n)。解决了上面的合并有序数列问题,再来看归并排序,其的基本思路就是将数组分成二组A,B,如果这二组组内的数据都是有序的,那么就可以很方便的将这二组数据进行排序。如何让这二组组内数据有序了?可以将A,B组各自再分成二组。依次类推,当分出来的小组只有一个数据时,可以认为这个小组组内已经达到了有序,然后再合并相邻的二个小组就可以了。这样通过先递归的分解数列,再合并数列就完成了归并排序。下面是归并排序代码:

  1. package com.xtfggef.algo.sort;  
  2. public class MergeSort {  
  3.     private static void mergeSort(int[] data, int start, int end) {  
  4.         if (end > start) {  
  5.             int pos = (start + end) / 2;  
  6.             mergeSort(data, start, pos);
  7.             mergeSort(data, pos + 1, end);  
  8.             merge(data, start, pos, end);
  9.         }
  10.     }
  11.     private static void merge(int[] data, int start, int pos, int end) {  
  12.         int len1 = pos – start + 1;  
  13.         int len2 = end – pos;  
  14.         int A[] = new int[len1 + 1];  
  15.         int B[] = new int[len2 + 1];  
  16.         for (int i = 0; i < len1; i++) {  
  17.             A[i] = data[i + start – 1];  
  18.         }
  19.         A[len1] = Integer.MAX_VALUE;
  20.         for (int i = 0; i < len2; i++) {  
  21.             B[i] = data[i + pos];
  22.         }
  23.         B[len2] = Integer.MAX_VALUE;
  24.         int m = 0, n = 0;  
  25.         for (int i = start – 1; i < end; i++) {  
  26.             if (A[m] > B[n]) {  
  27.                 data[i] = B[n];
  28.                 n++;
  29.             } else {  
  30.                 data[i] = A[m];
  31.                 m++;
  32.             }
  33.         }
  34.     }
  35.     private static void print(int[] data) {  
  36.         for (int i = 0; i < data.length; i++) {  
  37.             System.out.print(data[i] + ” “);  
  38.         }
  39.     }
  40.     public static void main(String args[]) {  
  41.         int data[] = { 8, 16, 99, 732, 10, 1, 29, 66 };  
  42.         print(data);
  43.         System.out.println();
  44.         mergeSort(data, 1, data.length);  
  45.         print(data);
  46.     }
  47. }

 

七、希尔排序(不稳定、O(nlogn))

d1 = n/2,d2 = d1/2 …

举例一下:{9,8,7,6,5,4,3,2,1,0} 10个数,现分为5组(9,4),(8,3),(7,2),(6,1),(5,0),然后分别对每组进行直接插入排序得到:

(4,9),(3,8),(2,7),(1,6),(0,5),再将这5组分为2组(4,3,2,1,0),(9,8,7,6,5)分别对这两组进行直插排序,得:(0,1,2,3,4),(5,6,7,8,9)*终有序。

  1. package com.xtfggef.algo.sort;  
  2. public class ShellSort {  
  3.     static void shellsort(int[] a, int n) {  
  4.         int i, j, temp;  
  5.         int gap = 0;  
  6.         while (gap <= n) {  
  7.             gap = gap * 3 + 1;  
  8.         }
  9.         while (gap > 0) {  
  10.             for (i = gap; i < n; i++) {  
  11.                 j = i – gap;
  12.                 temp = a[i];
  13.                 while ((j >= 0) && (a[j] > temp)) {  
  14.                     a[j + gap] = a[j];
  15.                     j = j – gap;
  16.                 }
  17.                 a[j + gap] = temp;
  18.             }
  19.             gap = (gap – 1) / 3;  
  20.         }
  21.     }
  22.     static void print(int data[]) {  
  23.         for (int i = 0; i < data.length; i++) {  
  24.             System.out.print(data[i] + ” “);  
  25.         }
  26.     }
  27.     public static void main(String[] args) {  
  28.         int data[] = { 2, 68, 7, 19, 1, 28, 66, 200 };  
  29.         print(data);
  30.         System.out.println();
  31.         shellsort(data, data.length);
  32.         print(data);
  33.     }
  34. }

八、多路快排

JDK1.8中Arrays.sort()采用的排序算法,具有较快的时间复杂度和稳定性,基本思路为:

1. 选取两个中轴P1, P2。

2. 假设P1<P2,否则交换。

3. 过程中原数组会分为四个部分:小于中轴1,大于中轴2,介于两个中轴之间,未排序部分(刚开始除了两个中轴,其它元素都属于这部分)。

4. 开始后,从未排序部分选取一个数,和两个中轴作比较,然后放到合适的位置,一直到未排序部分无数据,结束一趟排序。

5. 递归地处理子数组,稳定排序,时间复杂度稳定为O(nlogn)。

详情可以参见我的另一篇博文《Java之美[从菜鸟到高手演练]之Arrays类及其方法分析》

 

九、其他排序

下面的一段转载自博友@清蒸水皮 — 补充于2015年1月14日

==============================================

计数排序

当输入的元素是 n 个 0 到 k 之间的整数时,它的运行时间是 O(n + k)。计数排序不是比较排序,排序的速度快于任何比较排序算法。

由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的*大值与*小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量时间和内存。例如:计数排序是用来排序0到100之间的数字的*好的算法,但是它不适合按字母顺序排序人名。但是,计数排序可以用在基数排序中的算法来排序数据范围很大的数组。

算法的步骤如下:

  1. 找出待排序的数组中*大和*小的元素
  2. 统计数组中每个值为i的元素出现的次数,存入数组C的第i项
  3. 对所有的计数累加(从C中的*个元素开始,每一项和前一项相加)
  4. 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1

贴上代码:

  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <time.h>  
  4. //对于排序的关键字范围,一定是0-99  
  5. #define NUM_RANGE (100)  
  6. void print_arr(int *arr, int n)  
  7. {
  8.        int i;  
  9.        for(i=0; i<n; i++){  
  10.                if(!i){  
  11.                        printf(“%d”, arr[i]);  
  12.                }else{  
  13.                        printf(” %d”, arr[i]);  
  14.                }
  15.        }
  16.        printf(“\n”);  
  17. }
  18. /* 
  19. 算法的步骤如下: 
  20.     1.找出待排序的数组中*大和*小的元素 
  21.     2.统计数组中每个值为i的元素出现的次数,存入数组C的第i项 
  22.     3.对所有的计数累加(从C中的*个元素开始,每一项和前一项相加) 
  23.     4.反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1 
  24. */  
  25. void counting_sort(int *ini_arr, int *sorted_arr, int n)  
  26. {
  27.        int *count_arr = (int *)malloc(sizeof(int) * NUM_RANGE);  
  28.        int i, j, k;  
  29.        //统计数组中,每个元素出现的次数  
  30.        for(k=0; k<NUM_RANGE; k++){  
  31.                count_arr[k] = 0;
  32.        }
  33.        for(i=0; i<n; i++){  
  34.                count_arr[ini_arr[i]]++;
  35.        }
  36.        for(k=1; k<NUM_RANGE; k++){  
  37.                count_arr[k] += count_arr[k-1];
  38.        }
  39.        for(j=n-1 ; j>=0; j–){  
  40.            int elem = ini_arr[j];  
  41.            int index = count_arr[elem]-1;  
  42.            sorted_arr[index] = elem;
  43.            count_arr[elem]–;
  44.        }
  45.        free(count_arr);
  46. }
  47. int main(int argc, char* argv[])  
  48. {
  49.        int n;  
  50.        if(argc < 2){  
  51.                n = 10;
  52.        }else{  
  53.                n = atoi(argv[1]);
  54.        }
  55.        int i;  
  56.        int *arr = (int *)malloc(sizeof(int) * n);  
  57.        int *sorted_arr = (int *)malloc(sizeof(int) *n);  
  58.        srand(time(0));
  59.        for(i=0; i<n; i++){  
  60.                arr[i] = rand() % NUM_RANGE;
  61.        }
  62.        printf(“ini_array: “);  
  63.        print_arr(arr, n);
  64.        counting_sort(arr, sorted_arr, n);
  65.        printf(“sorted_array: “);  
  66.        print_arr(sorted_arr, n);
  67.        free(arr);
  68.        free(sorted_arr);
  69.        return 0;  
  70. }

桶排序

桶排序的基本思想

假设有一组长度为N的待排关键字序列K[1….n]。首先将这个序列划分成M个的子区间(桶) 。然后基于某种映射函数 ,将待排序列的关键字k映射到第i个桶中(即桶数组B的下标 i) ,那么该关键字k就作为B[i]中的元素(每个桶B[i]都是一组大小为N/M的序列)。接着对每个桶B[i]中的所有元素进行比较排序(可以使用快排)。然后依次枚举输出B[0]….B[M]中的全部内容即是一个有序序列。假如待排序列K= {49、 38 、 35、 97 、 76、 73 、 27、 49 }。这些数据全部在1—100之间。因此我们定制10个桶,然后确定映射函数f(k)=k/10。则*个关键字49将定位到第4个桶中(49/10=4)。依次将所有关键字全部堆入桶中,并在每个非空的桶中进行快速排序。

桶排序代价分析
桶排序利用函数的映射关系,减少了几乎所有的比较工作。实际上,桶排序的f(k)值的计算,其作用就相当于快排中划分,已经把大量数据分割成了基本有序的数据块(桶)。然后只需要对桶中的少量数据做先进的比较排序即可。

对N个关键字进行桶排序的时间复杂度分为两个部分:
(1) 循环计算每个关键字的桶映射函数,这个时间复杂度是O(N)。
(2) 利用先进的比较排序算法对每个桶内的所有数据进行排序,其时间复杂度为 ∑ O(Ni*logNi) 。其中Ni 为第i个桶的数据量。

很显然,第(2)部分是桶排序性能好坏的决定因素。尽量减少桶内数据的数量是提高效率的唯一办法(因为基于比较排序的*好平均时间复杂度只能达到O(N*logN)了)。因此,我们需要尽量做到下面两点:
(1) 映射函数f(k)能够将N个数据平均的分配到M个桶中,这样每个桶就有[N/M]个数据量。
(2) 尽量的增大桶的数量。*限情况下每个桶只能得到一个数据,这样就完全避开了桶内数据的“比较”排序操作。 当然,做到这一点很不容易,数据量巨大的情况下,f(k)函数会使得桶集合的数量巨大,空间浪费严重。这就是一个时间代价和空间代价的权衡问题了。

对于N个待排数据,M个桶,平均每个桶[N/M]个数据的桶排序平均时间复杂度为:
O(N)+O(M*(N/M)*log(N/M))=O(N+N*(logN-logM))=O(N+N*logN-N*logM)
当N=M时,即*限情况下每个桶只有一个数据时。桶排序的*好效率能够达到O(N)。

总结: 桶排序的平均时间复杂度为线性的O(N+C),其中C=N*(logN-logM)。如果相对于同样的N,桶数量M越大,其效率越高,*好的时间复杂度达到O(N)。 当然桶排序的空间复杂度 为O(N+M),如果输入数据非常庞大,而桶的数量也非常多,则空间代价无疑是昂贵的。此外,桶排序是稳定的。
我个人还有一个感受:在查找算法中,基于比较的查找算法*好的时间复杂度也是O(logN)。比如折半查找、平衡二叉树、红黑树等。但是Hash表却有O(C)线性级别的查找效率(不冲突情况下查找效率达到O(1))。大家好好体会一下:Hash表的思想和桶排序是不是有一曲同工之妙呢?

 

基数排序

上面的问题是多关键字的排序,但单关键字也仍然可以使用这种方式。
比如字符串“abcd” “aesc” “dwsc” “rews”就可以把每个字符看成一个关键字。另外还有整数 425、321、235、432也可以每个位上的数字为一个关键字。

基数排序的思想就是将待排数据中的每组关键字依次进行桶分配。比如下面的待排序列:
278、109、063、930、589、184、505、269、008、083
我们将每个数值的个位,十位,百位分成三个关键字: 278 -> k1(个位)=8 ,k2(十位)=7 ,k3=(百位)=2。
然后从*低位个位开始(从*次关键字开始),对所有数据的k1关键字进行桶分配(因为,每个数字都是 0-9的,因此桶大小为10),再依次输出桶中的数据得到下面的序列。
930、063、083、184、505、278、008、109、589、269
再对上面的序列接着进行针对k2的桶分配,输出序列为:
505、008、109、930、063、269、278、083、184、589
*后针对k3的桶分配,输出序列为:
008、063、083、109、184、269、278、505、589、930

性能分析
很明显,基数排序的性能比桶排序要略差。每一次关键字的桶分配都需要O(N)的时间复杂度,而且分配之后得到新的关键字序列又需要O(N)的时间复杂度。假如待排数据可以分为d个关键字,则基数排序的时间复杂度将是O(d*2N) ,当然d要远远小于N,因此基本上还是线性级别的。基数排序的空间复杂度为O(N+M),其中M为桶的数量。一般来说N>>M,因此额外空间需要大概N个左右。

但是,对比桶排序,基数排序每次需要的桶的数量并不多。而且基数排序几乎不需要任何“比较”操作,而桶排序在桶相对较少的情况下,桶内多个数据必须进行基于比较操作的排序。因此,在实际应用中,基数排序的应用范围更加广泛。

Activity的启动模式全解standard,singleTop,singleTask,singleInstance

Activity的启动模式全解standard,singleTop,singleTask,singleInstance

在android中控制Activity的启动模式的属性主要控制两大功能:

1,控制activity 进入哪一个任务task 中,   有两种可能,进入启动task中,进入指定taskAffinity的task中,如果指定taskAffinity的task还不存在,则创建一个

2,控制activity 多次启动的处理模式,       有三种可能,每次都创建新的,如果在顶部不创建新的,  如果存在则清除之上所有的activity

 

activity的taskAffinity属性值默认为application的taskAffinity属性值,application的taskAffinity属性值默认为包名

手动设置taskAffinity属性值时,可以设置任意字符串但是必须包含至少一个’.’点符号,否则apk会在安装时解析包错误

Activity的启动模式中多次启动的处理模式要先确定activity进入的task

activity 的launchMode 静态设置时有四种模式,动态设置(intent flag)时常用的有三种 ,其中让taskAffinity属性起作用的有两种模式 singleTaskFLAG_ACTIVITY_NEW_TASK ,其他launchMode启动模式taskAffinity属性无效

  • launchMode      standard      进入启动task,每次都创建新的实例进入task顶部
    singleTop    进入启动task,如果已有实例并且在task顶部不创建新实例,调用原实例的onNewIntent(),其它情况都创建新的实例进入task顶部

    •                singleTask   进入指定taskAffinity的task,如果指定的task存在,将task移到前台,如果指定task不存在,创建指定taskAffinity的task
    •                                    如果task中存在实例,则移除实例之上的所有实例并显示出来,执行原实例的onNewIntent(),其它情况则创建实例进入task顶部
    •               singleInstance   进入新的task,并且此task内只存在此一个activity ,不再加入别的activity
    •                                          如果task中存在实例,执行实例的onNewIntent()
  • taskAffinity起作用时:       进入指定taskAffinity的task,如果指定的task存在,将task移到前台,如果指定task不存在,创建指定taskAffinity的task

 

 

 

而在Intent当中,flag属性控制activity的启动模式:

FLAG_ACTIVITY_NEW_TASK    进入指定taskAffinity的task,如果指定的task存在,将task移到前台,如果指定task不存在,创建指定taskAffinity的task

每次都创建新的实例进入task顶部

     FLAG_ACTIVITY_SINGLE_TOP   进入启动task

                                                         如果已有实例并且在task顶部不创建新实例,执行实例的onNewIntent(),其它情况都创建新的实例进入task顶部

 

     FLAG_ACTIVITY_CLEAR_TOP    进入启动task

                                                         如果task中存在实例,则移除实例之上的所有实例,如果启动的activity启动模式不是standard模式,或者flag有FLAG_ACTIVITY_SINGLE_TOP属性

                                                         那么调用Activity B的onNewIntent()方法否则销毁原有实例创建新实例进入task顶部,其它情况则创建实例进入task顶部

 

 

 

 

对activity的启动模式属性中Intent的flag属性覆盖<activity>元素中属性设置;

 

在<activity>元素中,有以下几个属性是可以使用的:

  • taskAffinity
  • launchMode
  • allowTaskReparenting
  • clearTaskOnLaunch
  • alwaysRetainTaskState
  • finishOnTaskLaunch

 

而在Intent当中,有以下几个flag是比较常用的:

  • FLAG_ACTIVITY_NEW_TASK
  • FLAG_ACTIVITY_CLEAR_TOP
  • FLAG_ACTIVITY_SINGLE_TOP

 

 

处理affinity

affinity可以用于指定一个Activity更加愿意依附于哪一个任务,在默认情况下,同一个应用程序中的所有Activity都具有相同的affinity,所以,这些Activity都更加倾向于运行在相同的任务当中。当然了,你也可以去改变每个Activity的affinity值,通过<activity>元素的taskAffinity属性就可以实现了。

 

taskAffinity属性接收一个字符串参数,你可以指定成任意的值(经我测试字符串中至少要包含一个.),但必须不能和应用程序的包名相同,因为系统会使用包名来作为默认的affinity值。

 

affinity主要有以下两种应用场景:

  • 当调用startActivity()方法来启动一个Activity时,默认是将它放入到当前的任务当中。但是,如果在Intent中加入了一个FLAG_ACTIVITY_NEW_TASK flag的话(或者该Activity在manifest文件中声明的启动模式是”singleTask”),系统就会尝试为这个Activity单独创建一个任务。但是规则并不是只有这么简单,系统会去检测要启动的这个Activity的affinity和当前任务的affinity是否相同,如果相同的话就会把它放入到现有任务当中,如果不同则会去创建一个新的任务。而同一个程序中所有Activity的affinity默认都是相同的,这也是前面为什么说,同一个应用程序中即使声明成”singleTask”,也不会为这个Activity再去创建一个新的任务了。
  • 当把Activity的allowTaskReparenting属性设置成true时,Activity就拥有了一个转移所在任务的能力。具体点来说,就是一个Activity现在是处于某个任务当中的,但是它与另外一个任务具有相同的affinity值,那么当另外这个任务切换到前台的时候,该Activity就可以转移到现在的这个任务当中。
    那还是举一个形象点的例子吧,比如有一个天气预报程序,它有一个Activity是专门用于显示天气信息的,这个Activity和该天气预报程序的所有其它Activity具体相同的affinity值,并且还将allowTaskReparenting属性设置成true了。这个时候,你自己的应用程序通过Intent去启动了这个用于显示天气信息的Activity,那么此时这个Activity应该是和你的应用程序是在同一个任务当中的。但是当把天气预报程序切换到前台的时候,这个Activity又会被转移到天气预报程序的任务当中,并显示出来,因为它们拥有相同的affinity值,并且将allowTaskReparenting属性设置成了true。

清空返回栈

 

如何用户将任务切换到后台之后过了很长一段时间,系统会将这个任务中除了*底层的那个Activity之外的其它所有Activity全部清除掉。当用户重新回到这个任务的时候,*底层的那个Activity将得到恢复。这个是系统默认的行为,因为既然过了这么长的一段时间,用户很有可能早就忘记了当时正在做什么,那么重新回到这个任务的时候,基本上应该是要去做点新的事情了。

 

当然,既然说是默认的行为,那就说明我们肯定是有办法来改变的,在<activity>元素中设置以下几种属性就可以改变系统这一默认行为:

 

alwaysRetainTaskState

如果将*底层的那个Activity的这个属性设置为true,那么上面所描述的默认行为就将不会发生,任务中所有的Activity即使过了很长一段时间之后仍然会被继续保留。

 

clearTaskOnLaunch

如果将*底层的那个Activity的这个属性设置为true,那么只要用户离开了当前任务,再次返回的时候就会将*底层Activity之上的所有其它Activity全部清除掉。简单来讲,就是一种和alwaysRetainTaskState完全相反的工作模式,它保证每次返回任务的时候都会是一种初始化状态,即使用户仅仅离开了很短的一段时间。

 

finishOnTaskLaunch

这个属性和clearTaskOnLaunch是比较类似的,不过它不是作用于整个任务上的,而是作用于单个Activity上。如果某个Activity将这个属性设置成true,那么用户一旦离开了当前任务,再次返回时这个Activity就会被清除掉。

Activity的启动模式与flag详解

Activity的启动模式与flag详解

Activity有四种加载模式:standard(默认), singleTop, singleTask和 singleInstance。以下逐一举例说明他们的区别:

standard:Activity的默认加载方法,即使某个Activity在 Task栈中已经存在,另一个activity通过Intent跳转到该activity,同样会新创建一个实例压入栈中。例如:现在栈的情况为:A B C D,在D这个Activity中通过Intent跳转到D,那么现在的栈情况为: A B C D D 。此时如果栈顶的D通过Intent跳转到B,则栈情况为:A B C D D B。此时如果依次按返回键,D  D C B A将会依次弹出栈而显示在界面上。

singleTop:如果某个Activity的Launch mode设置成singleTop,那么当该Activity位于栈顶的时候,再通过Intent跳转到本身这个Activity,则将不会创建一个新的实例压入栈中。例如:现在栈的情况为:A B C D。D的Launch mode设置成了singleTop,那么在D中启动Intent跳转到D,那么将不会新创建一个D的实例压入栈中,此时栈的情况依然为:A B C D。但是如果此时B的模式也是singleTop,D跳转到B,那么则会新建一个B的实例压入栈中,因为此时B不是位于栈顶,此时栈的情况就变成了:A B C D B。

singleTask:如果某个Activity是singleTask模式,那么Task栈中将会只有一个该Activity的实例。例如:现在栈的情况为:A B C D。B的Launch mode为singleTask,此时D通过Intent跳转到B,则栈的情况变成了:A B。而C和D被弹出销毁了,也就是说位于B之上的实例都被销毁了。

singleInstance:将Activity压入一个新建的任务栈中。例如:Task栈1的情况为:A B C。C通过Intent跳转到D,而D的Launch mode为singleInstance,则将会新建一个Task栈2。此时Task栈1的情况还是为:A B C。Task栈2的情况为:D。此时屏幕界面显示D的内容,如果这时D又通过Intent跳转到D,则Task栈2中也不会新建一个D的实例,所以两个栈的情况也不会变化。而如果D跳转到C,则栈1的情况变成了:A B C C,因为C的Launch mode为standard,此时如果再按返回键,则栈1变成:A B C。也就是说现在界面还显示C的内容,不是D。
好了,现在有一个问题就是这时这种情况下如果用户点击了Home键,则再也回不到D的即时界面了。如果想解决这个问题,可以为D在Manifest.xml文件中的声明加上<intent-filter>
<action android:name=”android.intent.action.MAIN” />
<category android:name=”android.intent.category.LAUNCHER” />
</intent-filter>

加上这段之后,也就是说该程序中有两个这种声明,另一个就是那个正常的根 activity,在打成apk包安装之后,在程序列表中能看到两个图标,但是如果都运行的话,在任务管理器中其实也只有一个。上面的情况点击D的那个图标就能回到它的即时界面(比如一个EditText,以前输入的内容,现在回到之后依然存在)。

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Intent的常用Flag参数:

FLAG_ACTIVITY_CLEAR_TOP:例如现在的栈情况为:A B C D 。D此时通过intent跳转到B,如果这个intent添加FLAG_ACTIVITY_CLEAR_TOP 标记,则栈情况变为:A B。如果没有添加这个标记,则栈情况将会变成:A B C D B。也就是说,如果添加了FLAG_ACTIVITY_CLEAR_TOP 标记,并且目标Activity在栈中已经存在,则将会把位于该目标activity之上的activity从栈中弹出销毁。这跟上面把B的Launch mode设置成singleTask类似。

FLAG_ACTIVITY_NEW_TASK:例如现在栈1的情况是:A B C。C通过intent跳转到D,并且这个intent添加了FLAG_ACTIVITY_NEW_TASK 标记,如果D这个Activity在Manifest.xml中的声明中添加了Task affinity,并且和栈1的affinity不同,系统首先会查找有没有和D的Task affinity相同的task栈存在,如果有存在,将D压入那个栈,如果不存在则会新建一个D的affinity的栈将其压入。如果D的Task affinity默认没有设置,或者和栈1的affinity相同,则会把其压入栈1,变成:A B C D,这样就和不加FLAG_ACTIVITY_NEW_TASK 标记效果是一样的了。      注意如果试图从非activity的非正常途径启动一个activity,比如从一个service中启动一个activity,则intent比如要添加FLAG_ACTIVITY_NEW_TASK 标记。

FLAG_ACTIVITY_NO_HISTORY:例如现在栈情况为:A B C。C通过intent跳转到D,这个intent添加FLAG_ACTIVITY_NO_HISTORY标志,则此时界面显示D的内容,但是它并不会压入栈中。如果按返回键,返回到C,栈的情况还是:A B C。如果此时D中又跳转到E,栈的情况变为:A B C E,此时按返回键会回到C,因为D根本就没有被压入栈中。

FLAG_ACTIVITY_SINGLE_TOP:和上面Activity的 Launch mode的singleTop类似。如果某个intent添加了这个标志,并且这个intent的目标activity就是栈顶的activity,那么将不会新建一个实例压入栈中。

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Activity的主要属性:

allowTaskReparenting:设置成true时,和Intent的FLAG_ACTIVITY_NEW_TASK 标记类似。

alwaysRetainTaskStat:   如果用户长时间将某个task 移入后台,则系统会将该task的栈内容弹出只剩下栈底的activity,此时用户再返回,则只能看到根activity了。如果栈底的 activity的这个属性设置成true,则将阻止这一行为,从而保留所有的栈内容。

clearTaskOnLaunch:根activity的这个属性设置成true时,和上面的alwaysRetainTaskStat 的属性为true情况搞好相反。

finishOnTaskLaunch:对于任何activity,如果它的这个属性设置成true,则当task被放置到后台,然后重新启动后,该activity将不存在了

mac专有软件_什么是专有软件?

mac专有软件

Software is the core of the IT and different software has different license types to specify and restrict software usage. Proprietary Software is a software type which is labeled as non-free software or closed-source software. In general the proprietary software is owned by the creator and retains intellectual property rights.

软件是IT的核心,不同的软件具有不同的许可证类型来指定和限制软件使用。 Proprietary Software是一种软件类型,被标记non-free softwareclosed-source software 。 通常,专有软件归创建者所有,并保留知识产权。

专有软件特性 (Proprietary Software Characteristic)

Proprietary software has the following characteristic.

专有软件具有以下特征。

  • Proprietary software generally sold and bought for a price.专有软件通常以一定价格出售和购买。
  • Proprietary software owned by the developed, company, or owner and the user does not have any intellectual right.开发者,公司或所有者以及用户拥有的专有软件没有任何知识产权。
  • Proprietary software does not provide its source code and prohibits change of the source code.专有软件不提供其源代码,并禁止更改源代码。

什么是.htpasswd文件?

.htpasswd is a flat-file used to store usernames and password. This file is generally used by the web server software like Apache, Nginx, etc. in order to verify the users via HTTP basic authentication.

.htpasswd是用于存储用户名和密码的平面文件。 该文件通常由Web服务器软件(例如Apache,Nginx等)使用,以便通过HTTP基本身份验证来验证用户。

什么是.htpasswd文件内容? (What Are .htpasswd File Contents?)

.htpasswd is a flat or text file that contains ASCII text. .htpaswd file structures are very simple where every line stores a username and related passwords. The user name and password are delimited with a colon sign. Also, the password is stored in an encrypted way, not a clear text format in order to make the password secure. Below we can see that there are 3 lines where users ismail, ahmet, ali are configured with their encrypted password.

.htpasswd是包含ASCII文本的平面文件或文本文件。 .htpaswd文件结构非常简单,其中每一行都存储一个用户名和相关密码。 用户名和密码用冒号分隔。 同样,密码以加密方式(不是明文格式)存储,以确保密码安全。 在下面我们可以看到在3行中,用户ismail,ahmet,ali用其加密密码进行了配置。

  1. ismail:$apr1$/5EzxSg3$SXVemrqNIb/TrKvJv4Z5r0
  2. ahmet:$apr1$cEKbD/Wa$M012WG8Txqp/dhso8.znk0
  3. ali:$apr1$o/t1Efly$E7798GsGjMWoNUpqmG4l60

如何创建.httpaswd文件? (How To Create .httpaswd File?)

The .htpasswd file can be created by using htpasswd command or the touch command or a text editor. But the most appropriate way is using the httpasswd command. Also the .htpasswd file content can be edited by using the htpasswd command too. Below we will create the .htpasswd file by using the htpasswd command where we will provide the file name with the -c option.

可以使用htpasswd命令或touch命令或文本编辑器来创建.htpasswd文件。 但是*合适的方法是使用httpasswd命令。 同样,也可以使用htpasswd命令来编辑.htpasswd文件内容。 下面,我们将使用htpasswd命令创建.htpasswd文件,在该文件中,我们将使用-c选项提供文件名。

$ htpasswd -c .htpasswd ismail
%title插图%num
How To Create .httpaswd File?
如何创建.httpaswd文件?

Or for the .htpasswd file we can specify the complete path or full path like below.

或者,对于.htpasswd文件,我们可以指定完整路径或完整路径,如下所示。

$ htpasswd -c /var/www/mysite/.htpasswd ismail

Alternatively, we can use the touch command where the created .htpasswd file will be an empty file with just its name.

或者,我们可以使用touch命令,其中创建的.htpasswd文件将是一个只有其名称的空文件。

$ touch .htpasswd

Another alternative to create .htpasswd file is used a command line or GUI text editor. As an example we will use the nano text editor but alternatively the text editors like vi, vim, KWrite can be used.

创建.htpasswd文件的另一种方法是使用命令行或GUI文本编辑器。 作为示例,我们将使用nano文本编辑器,但也可以使用vi,vim,KWrite等文本编辑器。

$ nano .htpasswd

配置.htpasswd文件 (Configure .htpasswd File)

As .htpasswd file is used for Apache and related web server software to create user authentication we should configures the web server to use the .htpasswd file. Below we will enable the HTTP basic authentication by specifying the .htpasswd file for username and password.

由于.htpasswd文件用于Apache和相关的Web服务器软件来创建用户身份验证,因此我们应将Web服务器配置为使用.htpasswd文件。 下面,我们通过为用户名和密码指定.htpasswd文件来启用HTTP基本认证。

  1. AuthUserFile /var/www/mysite/.htpasswd
  2. AuthType Basic
  3. AuthName “Poftut HTTP Authentication”
  4. Require valid-user
LEARN MORE  Understanding and Configuring Apache Access Log

什么是非字母数字字符?

Characters are used to express letters, numbers, signs, etc. Characters are categorized as alphanumeric and non-alphanumeric characters. Alphanumeric characters are consist of alphabet characters and numeric characters. Other than alphanumeric characters are called as non-alphanumeric characters.

字符用于表示字母,数字,符号等。字符分为字母数字字符和非字母数字字符。 字母数字字符由字母字符和数字字符组成。 字母数字字符以外的字符称为非字母数字字符。

字母数字字符 (Alphanumeric Characters)

Alphanumeric characters consist of alphabet and numeric characters. Alphabet characters can be lower case or upper case.

字母数字字符由字母和数字字符组成。 字母字符可以是小写或大写。

非字母数字字符 (Non-Alphanumeric Characters)

Non-Alphanumeric characters are except alphanumeric characters. space, percent sign, underscore, pipe, colon, semicolon, etc are non-alphanumeric characters. They can be also categorized as punctuation characters, symbol characters, etc.

非字母数字字符是字母数字字符以外的字符。 空格,百分号,下划线,竖线,冒号,分号等均为非字母数字字符。 它们也可以分为标点符号,符号字符等。

  1. (blank space)
  2. ~ (tilde)
  3. ` (grave accent)
  4. ! (exclamation mark)
  5. @ (at)
  6. # (pound)
  7. $ (dollar sign)
  8. % (percent)
  9. ^ (carat)
  10. & (ampersand)
  11. * (asterisk)
  12. ( (open parenthesis)
  13. ) (close parenthesis)
  14. _ (underscore)
  15. (hyphen)
  16. + (plus sign)
  17. = (equals)
  18. { (open brace)
  19. } (close brace)
  20. [ (open bracket)
  21. ] (close bracket)
  22. | (pipe)
  23. \ (backslash)
  24. : (colon)
  25. ; (semicolon)
  26. < (less than)
  27. , (comma)
  28. > (greater than)
  29. . (period)
  30. ? (question mark)
  31. / (forward slash)
LEARN MORE  Python Glob() Function To Match Path, Directory, File Names with Examples
了解更多Python Glob()函数以示例匹配路径,目录,文件名

千兆以太网 crc_什么是千兆以太网?

千兆以太网 crc

Gigabit Ethernet is a speed definition for the Ethernet protocol which can provide 1-gigabit data transmission per second. 1 Gigabit is equal to the 1,000Mbps speed. Gigabit Ethernet also refers to an ethernet port which can provide 1-gigabit speed.

千兆以太网是以太网协议的速度定义,可以每秒提供1千兆位数据传输。 1千兆比特等于1,000Mbps的速度。 千兆以太网还指可以提供1千兆位速度的以太网端口。

千兆以太网标准 (Gigabit Ethernet Standard)

Gigabit Ethernet is an established standard which is mainly described IEEE 1000Base-T (802.3ab) and later TIA 1000Base-TX is created which requires CAT6 cable to work in a reliable fashion. Other relavent standards are

千兆以太网是已建立的标准,主要描述IEEE 1000Base-T(802.3ab),后来创建了TIA 1000Base-TX,它要求CAT6电缆以可靠的方式工作。 其他相关标准是

  • 1000BASE-CX is the first Gigabit Ethernet standard which works in 25 meters.1000BASE-CX是首个可在25米范围内工作的千兆以太网标准。
  • 1000BASE-SX is a fiber cable Gigabit Ethernet standard which works up to 550 meters.1000BASE-SX是光纤电缆千兆位以太网标准,可工作达550米。
  • 1000BASE-LX is another fiber optic cable standard which works up to 5 kilometers.1000BASE-LX是另一种*长可达5公里的光缆标准。
  • 1000BASE-T is coaxial cable or CAT5E or CAT6 standard which works up to 100 meters.1000BASE-T是同轴电缆或CAT5E或CAT6标准,*长可达100米。

千兆以太网连接(端口和电缆) (Gigabit Ethernet Connection (Port and Cable))

Gigabit is a very fast connection wherein a single second 120 MB or megabyte data can be transferred theoretically. But in the real world, it may a bit lower related to the remote system speed. As a fast connection, Gigabit Ethernet port and cable should meet some requirements which are generally CAT5e or CAT6 cable or fiber cable. Even fiber cable provides less delay creating a fiber cable infrastructure that is hard and expensive. Generally, CAT6 cables are used to connect network devices like modem, switch router, firewall, computer with each other. As a connector like other ethernet connections, RJ-45 is used.

千兆位是非常快速的连接,其中理论上可以传输单个第二个120 MB或兆字节的数据。 但是在现实世界中,它与远程系统速度有关可能会更低。 作为快速连接,千兆位以太网端口和电缆应满足一些要求,通常是CAT5e或CAT6电缆或光缆。 即使是光缆,也可以减少延迟,从而创建既硬又昂贵的光缆基础结构。 通常,CAT6电缆用于将网络设备(例如调制解调器,交换机路由器,防火墙,计算机)相互连接。 与其他以太网连接一样,使用RJ-45作为连接器。

如何在Windows中列出千兆以太网 (How To List Gigabit Ethernet in Windows)

In windows to get information about the Gigabit Ethernet the Control Panel -> Network and Internet -> Network Connections steps should be following and the following screen will list existing network connections. Generally the network connection name will contain Gigabit term which refers to the Gigabit Ethernet.

在要获取有关千兆以太网的信息的窗口中,应遵循“ Control Panel -> Network and Internet -> Network Connections步骤,以下屏幕将列出现有的网络连接。 通常,网络连接名称将包含Gigabit术语,该术语指的是千兆以太网。

LEARN MORE  What Is Dial-Up Internet Connection?
了解更多什么是拨号Internet连接?
%title插图%num

Also double click to the interface will show detailed information where also speed will list 1.0 Gbps which is gigabit connection.

同样双击该界面将显示详细信息,其中速度还将列出1.0 Gbps ,这是千兆位连接。

%title插图%num
1 Gigabit Ethernet
1个千兆以太网

千兆以太网与快速以太网(Gigabit Ethernet vs Fast Ethernet)

Fast Ethernet is another ethernet connection type or speed which is one level lower than Gigabit ethernet. Gigabit ethernet provides 1 Gbps or 1.000 Mbps speed where Fast Ethernet is 100Mbps . This means Gigabit ethernet has 10 times more bandwidth then fast ethernet. Also fast ethernet can work with CAT5 but gigabit can only work with CAT5E.

快速以太网是另一种以太网连接类型或速度,比千兆以太网低一个级别。 千兆以太网提供1 Gbps或1.000 Mbps的速度,而快速以太网为100Mbps。 这意味着千兆以太网的带宽是快速以太网的10倍。 快速以太网也可以与CAT5一起使用,但千兆位只能与CAT5E一起使用。

如何在所有浏览器的Chrome,Firefox,Edge,Opera,Safari上禁用弹出窗口阻止程序?

Pop-ups are very old technology that is used in web sites or web applications in order to interact, warn, or make the user focus on a specific content by creating new browser windows.

Pop-ups是一种非常古老的技术,用于网站或Web应用程序中,以便通过创建新的浏览器窗口进行交互,警告或使用户专注于特定内容。

什么是弹出窗口阻止程序? (What Is Pop-up Blocker?)

Pop-up blockers are used to block or prevent popups. Especially in the 2000s a lot of web sites were using popups. In general, popups are annoying for the web users and generally blocked by using builtin or 3rd party popups blockers provided by browsers. By default, most of the popular browsers enable builtin popup blockers and this can create problems in some cases.

弹出窗口阻止程序用于阻止或阻止弹出窗口。 特别是在2000年代,许多网站都使用弹出窗口。 通常,弹出窗口对Web用户来说很烦人,并且通常使用浏览器提供的内置或第三方弹出窗口阻止程序来阻止。 默认情况下,大多数流行的浏览器都启用内置的弹出窗口阻止程序,这在某些情况下可能会引起问题。

在Google Chrome上禁用弹出窗口阻止程序 (Disable Pop-up Blocker On Google Chrome)

In Google Chrome the built-in pop-up blocking feature is enabled by default. If we want to see the pop-ups we should disable the pop-up blocker for Google Chrome. In order to disable the pop-up blockers, we should navigate to the Pop-ups and redirects configuration on the Google Chrome settings. We can open the pop-ups and redirects configuration Menu Icon -> Settings -> Privacy and Security Category -> Site Settings -> Pop-ups and redirects steps. Or simply we can use following address which will directly open the Pop-ups and redirects the configuration screen.

在Google Chrome浏览器中,默认情况下启用了内置的弹出窗口阻止功能。 如果要查看弹出窗口,则应禁用Google Chrome浏览器的弹出窗口阻止程序。 为了禁用弹出窗口阻止程序,我们应该导航到“弹出窗口”并重定向Google Chrome设置上的配置。 我们可以打开弹出窗口并重定向配置菜单图标->设置->隐私和安全类别->网站设置->弹出窗口并重定向步骤。 或者简单地,我们可以使用以下地址,该地址将直接打开弹出窗口并重定向配置屏幕。

chrome://settings/content/popups

chrome:// settings / content / popups

We will just enable the Allowed configuration like below.

我们将仅启用如下所示的Allowed配置。

%title插图%num
Disable Pop-up Blocker On Google Chrome
在Google Chrome上禁用弹出窗口阻止程序

In the previous configuration, we have disabled the pop-up blocker completely for all web sites. But alternatively, we can disable pop-up blocker for specific sites and continue pop-up blocking for all other sites. First the Allowed configuration must be disabled and the Allow category should be added to new web sites.

在以前的配置中,我们完全禁用了所有网站的弹出窗口阻止程序。 但是,或者,我们可以禁用特定站点的弹出窗口阻止程序,并继续对所有其他站点进行弹出窗口阻止。 首先,必须禁用“ Allowed配置”,并且应将“ Allow类别添加到新的网站。

%title插图%num
Disable Pop-up Blocker For Specific Site In Google Chrome
在Google Chrome中为特定网站禁用弹出窗口阻止程序

In the following Add a site we will type the domain name of specific web pages of the site we want to disable pop-up blocking or simply enabling pop-ups. Below all pages about the poftut.com pop-ups will be enabled.

在下面的Add a site我们将键入要禁用弹出窗口阻止或仅启用弹出窗口的站点特定网页的域名。 在下面所有关于poftut.com弹出窗口的页面将被启用。

%title插图%num
Enable Pop-ups For Specific Web Site
启用特定网站的弹出窗口

Below we can see that pop-ups for poftut.com is enabled but disabled for all other web sites.

在下面我们可以看到poftut.com的弹出窗口已启用,但所有其他网站均被禁用。

%title插图%num

We can manage pop-up enabled specific web sites by using the web site menu like below. We can block, edit, or remove the web site or domain from this menu.

我们可以使用以下网站菜单来管理启用了弹出式窗口的特定网站。 我们可以从此菜单阻止,编辑或删除网站或域。

%title插图%num

在Mozilla Firefox上禁用弹出窗口阻止程序 (Disable Pop-up Blocker On Mozilla Firefox)

By default Mozilla Firefox built-in pop-up blocker is enable. We can disable Firefox pop-ups blocker from the Options menu. Options menu can be opened Firefox Menu -> Options -> Privacy&Security or directly using the followin address which will open the Privacy & Security screen.

默认情况下,Mozilla Firefox内置的弹出窗口阻止程序处于启用状态。 我们可以从“选项”菜单禁用Firefox弹出窗口阻止程序。 可以打开Firefox菜单->选项->隐私和安全性,或直接使用以下地址打开“隐私和安全性”屏幕,打开选项菜单。

LEARN MORE  How To Allow and Block Pop-ups In Chrome with Pop-up Blocker?
了解更多信息如何使用弹出窗口阻止程序在Chrome中允许和阻止弹出窗口?

about:preferences#privacy

关于:首选项#隐私

In the following screen, we will just uncheck the Block pop-up windows feature which will disable the pop-up blocker.

在以下屏幕中,我们将取消选中“阻止弹出窗口”功能,该功能将禁用弹出窗口阻止程序。

%title插图%num
Disable Pop-up Blocker On Mozilla Firefox
在Mozilla Firefox上禁用弹出窗口阻止程序

Alternatively the Exceptions screen can be used to allow pop-ups for specific web sites and domain. We will just type the domain or web site we want to allow pop-ups and click to the Allow button like below. The last step is click to the Save Changes button.

另外,“ Exceptions屏幕可用于允许弹出特定网站和域的消息。 我们将只键入要允许弹出窗口的域或网站,然后单击如下所示的“ Allow按钮。 *后一步是单击“ Save Changes按钮。

%title插图%num
Mozilla Firefox Pop-up Exceptions
Mozilla Firefox弹出窗口异常

在Microsoft Edge和Internet Explorer上禁用弹出窗口阻止程序(Disable Pop-up Blocker On Microsoft Edge and Internet Explorer)

Microsoft Edge is the successor of the Internet Explorer browser and following instructions can be also used for the Internet Explorer. We can navigate to the pop-ups configuration from Menu -> Options -> Privacy&Security .

Microsoft Edge是Internet Explorer浏览器的后继产品,并且以下说明也可以用于Internet Explorer。 我们可以从菜单->选项->隐私与安全导航到弹出窗口配置。

%title插图%num
Disable Pop-up Blocker On Microsoft Edge and Internet Explorer
在Microsoft Edge和Internet Explorer上禁用弹出窗口阻止程序

在Opera上禁用弹出窗口阻止程序(Disable Pop-up Blocker On Opera)

Opera is allowed pop-ups by default which is different from other popular browsers. But if we enabled the pop-up blocking we can allow pop-ups or disable pop-up blocker from the Settings -> Content -> Site Settings -> Pop-ups and redirects like below or we can use the following address to directly navigate to the pop-up configuration screen.

默认情况下,Opera允许弹出窗口,这与其他流行的浏览器不同。 但是,如果启用了弹出窗口阻止功能,则可以从“设置”->“内容”->“站点设置”->“弹出窗口和重定向”中允许弹出窗口或禁用弹出窗口阻止程序,如下所示,或者我们可以使用以下地址直接导航到弹出的配置屏幕。

opera://settings/content/popups

歌剧:// settings / content / popups

%title插图%num
Disable Pop-up Blocker On Opera
在Opera上禁用弹出窗口阻止程序

Alternative we can manage disabling pop-ups blocker for specific sites which is very similar to the Google Chrome from the Allow like below.

另外,我们可以管理针对特定网站的弹出窗口阻止程序,这与下面的“ Allow类似”中的Google Chrome浏览器非常相似。

%title插图%num
Disable Pop-up Blocker On Opera
在Opera上禁用弹出窗口阻止程序

在Safari上禁用弹出窗口阻止程序(Disable Pop-up Blocker On Safari)

Safari is Apple MacOS X specific browser. Like most of the other popular browsers, the pop-ups blocker is enabled by default and in order to show pop-ups, the blocker should be disabled. Safari built-in pop-ups blocker can be disabled from Options->Block Pop-up Windows

Safari是Apple MacOS X专用的浏览器。 像大多数其他流行的浏览器一样,默认情况下会启用弹出窗口阻止程序,并且为了显示弹出窗口,应禁用该阻止程序。 可以从“选项”->“阻止弹出窗口”禁用Safari内置的弹出窗口阻止程序

翻译自: https://www.poftut.com/how-to-disable-pop-up-blockers-on-chrome-firefox-edge-opera-safari-for-all-browsers/

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速